<?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>Docker &#8211; My Shitty Code</title>
	<atom:link href="https://myshittycode.com/tag/docker/feed/" rel="self" type="application/rss+xml" />
	<link>https://myshittycode.com</link>
	<description>Embracing the Messiness in Search of Epic Solutions</description>
	<lastBuildDate>Thu, 08 Aug 2024 22:05:49 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://myshittycode.com/wp-content/uploads/2022/04/cropped-icon-32x32.png</url>
	<title>Docker &#8211; My Shitty Code</title>
	<link>https://myshittycode.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">205304208</site>	<item>
		<title>Cloud Run: Connecting to Cloud SQL with Private IP and SSL</title>
		<link>https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/</link>
					<comments>https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Thu, 08 Aug 2024 22:05:43 +0000</pubDate>
				<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Programming Language]]></category>
		<category><![CDATA[Cloud Run]]></category>
		<category><![CDATA[Cloud SQL]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Google Cloud Platform]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2642</guid>

					<description><![CDATA[<p>There are various ways for a Cloud Run instance to connect to a Cloud SQL instance. Regardless of the solutions, if both resources reside in the same GCP organization, connecting using a Cloud SQL&#8217;s private IP with SSL/TLS encryption is highly recommended to prevent network traffic from going out to the internet. It is possible [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/">Cloud Run: Connecting to Cloud SQL with Private IP and SSL</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#cloud-sql-configuration">Cloud SQL Configuration</a></li><li><a href="#options">Options</a><ul><li><a href="#option-a-cloud-sql-language-connectors">Option A: Cloud SQL Language Connectors</a></li><li><a href="#option-b-managed-cloud-sql-proxy-in-cloud-run">Option B: Managed Cloud SQL Proxy in Cloud Run</a></li><li><a href="#option-c-manually-configure-cloud-sql-proxy">Option C: Manually Configure Cloud SQL Proxy</a></li><li><a href="#option-d-use-cloud-sq-ls-private-ip-and-client-certificate">Option D: Use Cloud SQL&#8217;s Private IP and Client Certificate</a></li></ul></li><li><a href="#recommendations">Recommendations</a><ul><li><a href="#best-solution-option-a">Best Solution &#8211; Option A</a></li><li><a href="#distance-next-best-solution-option-d">Distance Next Best Solution &#8211; Option D</a></li><li><a href="#least-favorite-solution-option-b-c">Least Favorite Solution &#8211; Option B &amp; C</a></li></ul></li></ul></nav></div>



<p class="wp-block-paragraph">There are various ways for a Cloud Run instance to connect to a Cloud SQL instance. Regardless of the solutions, if both resources reside in the same GCP organization, connecting using a Cloud SQL&#8217;s private IP with SSL/TLS encryption is highly recommended to prevent network traffic from going out to the internet.</p>



<p class="wp-block-paragraph">It is possible to have a Cloud SQL instance with both private and public IPs enabled, and there are legitimate reasons for doing so. For example, it allows the developers to connect to the Cloud SQL instance outside the GCP environment. However, utmost care is needed to ensure the private IP is always used when connecting from the Cloud Run instance.</p>



<p class="wp-block-paragraph">This article evaluates the pros and cons of each solution and provides some recommendations.</p>



<h2 class="wp-block-heading" id="cloud-sql-configuration">Cloud SQL Configuration</h2>



<p class="wp-block-paragraph">For consistency&#8217;s sake, SSL/TLS encryption is enabled on the Cloud SQL instance using client certificates.</p>



<figure class="wp-block-image size-large is-resized"><img fetchpriority="high" decoding="async" width="1024" height="742" src="https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1-1024x742.png?x45560" alt="Cloud SQL" class="wp-image-2651" style="width:800px" srcset="https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1-1024x742.png 1024w, https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1-300x217.png 300w, https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1-768x557.png 768w, https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1.png 1104w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">A client certificate is also created. </p>



<figure class="wp-block-image size-large is-resized"><img decoding="async" width="1024" height="417" src="https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1-1024x417.png?x45560" alt="" class="wp-image-2650" style="width:800px" srcset="https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1-1024x417.png 1024w, https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1-300x122.png 300w, https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1-768x313.png 768w, https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1.png 1096w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>Note:</strong> Some solutions, such as Cloud SQL Proxy and Cloud SQL Language Connectors, do not need the client certificate. So, these steps can be safely skipped. From the <a href="https://cloud.google.com/sql/docs/mysql/configure-ssl-instance#enforcing-ssl" target="_blank" rel="noopener">documentation</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">With Cloud SQL Auth Proxy and Cloud SQL Connectors, client and server identities are also automatically verified regardless of the SSL mode setting.</p>
</blockquote>



<h2 class="wp-block-heading" id="options">Options</h2>



<h3 class="wp-block-heading" id="option-a-cloud-sql-language-connectors">Option A: Cloud SQL Language Connectors</h3>



<p class="wp-block-paragraph">This solution uses <a href="https://cloud.google.com/sql/docs/mysql/connect-connectors" target="_blank" rel="noopener">Cloud SQL Language Connectors</a>, a programming language library maintained by Google.</p>



<p class="wp-block-paragraph">The following configurations are <strong>NOT</strong> required:</p>



<ul class="wp-block-list">
<li>SSL certificates in the Cloud SQL instance</li>



<li>Cloud SQL volume mounting in the Cloud Run instance.</li>
</ul>



<p class="wp-block-paragraph"><strong>Python App</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os

from google.cloud.sql.connector import Connector, IPTypes

# ... simplified for brevity

connector = Connector(IPTypes.PRIVATE)  # use Private IP

connector.connect(
  os.getenv(&#039;DB_INSTANCE_CONNECTION_NAME&#039;),  # project:region:instance
  &#039;pymysql&#039;,
  user=os.getenv(&#039;DB_USER&#039;),
  password=os.getenv(&#039;DB_PASS&#039;),
  db=os.getenv(&#039;DB_NAME&#039;),
)
</pre></div>


<p class="wp-block-paragraph"><strong>Pros</strong></p>



<ul class="wp-block-list">
<li>Very concise configuration in both IaC and application code since there&#8217;s no need to manage client certificates.</li>
</ul>



<p class="wp-block-paragraph"><strong>Cons</strong></p>



<ul class="wp-block-list">
<li>Only supports Python, Java, Go, and Node.js.</li>
</ul>



<h3 class="wp-block-heading" id="option-b-managed-cloud-sql-proxy-in-cloud-run">Option B: Managed Cloud SQL Proxy in Cloud Run</h3>



<p class="wp-block-paragraph">This solution uses a <a href="https://cloud.google.com/sql/docs/mysql/connect-run#configure" target="_blank" rel="noopener">managed Cloud SQL Proxy</a> that is configurable directly in the Cloud Run instance. Once configured, a Cloud SQL volume mount is created in the Cloud Run instance. </p>



<p class="wp-block-paragraph"><strong>Python App</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os

import pymysql

# ... simplified for brevity

pymysql.connect(
  database=os.getenv(&#039;DB_NAME&#039;),
  user=os.getenv(&#039;DB_USER&#039;),
  password=os.getenv(&#039;DB_PASS&#039;),
  unix_socket=f&#039;/cloudsql/{os.getenv(&quot;DB_INSTANCE_CONNECTION_NAME&quot;)}&#039;,  # /cloudsql/project:region:instance
)
</pre></div>


<p class="wp-block-paragraph"><strong>Pros</strong></p>



<ul class="wp-block-list">
<li>Very concise configuration in both IaC and application code since there&#8217;s no need to manage SSL certificates.</li>
</ul>



<p class="wp-block-paragraph"><strong>Cons</strong></p>



<ul class="wp-block-list">
<li><strong>It doesn&#8217;t work if the Cloud SQL instance has both public IP and private IP enabled</strong>. There&#8217;s no way to specify which IP to use, and Cloud SQL Proxy automatically uses the public IP if it exists.
<ul class="wp-block-list">
<li><strong>Note:</strong> If you know how to pull this off, please drop a comment below.</li>
</ul>
</li>



<li><a href="https://cloud.google.com/sql/docs/mysql/sql-proxy#production-environment" target="_blank" rel="noopener">Additional configurations</a> must be considered when running in production, which introduces many points of failure.</li>



<li>It may exceed the default <a href="https://cloud.google.com/sql/docs/mysql/connect-run#api-quota-limits" target="_blank" rel="noopener">Cloud SQL Admin API quota limit</a>.</li>
</ul>



<h3 class="wp-block-heading" id="option-c-manually-configure-cloud-sql-proxy">Option C: Manually Configure Cloud SQL Proxy</h3>



<p class="wp-block-paragraph">This solution <a href="https://cloud.google.com/sql/docs/mysql/connect-auth-proxy" target="_blank" rel="noopener">manually configures Cloud SQL Proxy</a> via Dockerfile instead of relying on the managed Cloud SQL volume mount from Cloud Run.</p>



<p class="wp-block-paragraph"><strong>Dockerfile</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; highlight: [13,14,16]; title: ; notranslate">
FROM python:3.12-alpine

RUN apk update &amp;amp;&amp;amp; apk add curl

WORKDIR /app

COPY . /app

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 8080

RUN curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.12.0/cloud-sql-proxy.linux.amd64
RUN chmod +x cloud-sql-proxy

CMD &#x5B;&quot;sh&quot;, &quot;-c&quot;, &quot;./cloud-sql-proxy --private-ip --port 3306 ${DB_INSTANCE_CONNECTION_NAME} &amp;amp; sleep 5 &amp;amp;&amp;amp; python app.py&quot;]
</pre></div>


<p class="wp-block-paragraph"><strong>Python App</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os

import pymysql

# ... simplified for brevity

pymysql.connect(
  host=os.getenv(&#039;DB_HOST&#039;),  # 127.0.0.1
  database=os.getenv(&#039;DB_NAME&#039;),
  user=os.getenv(&#039;DB_USER&#039;),
  password=os.getenv(&#039;DB_PASS&#039;),
)
</pre></div>


<p class="wp-block-paragraph"><strong>Pros</strong></p>



<ul class="wp-block-list">
<li>Very concise configuration in both IaC and application code since there&#8217;s no need to manage client certificates.</li>



<li>Unlike the managed Cloud SQL Proxy volume mount solution, this solution will work even if the Cloud SQL instance has both private and public IPs.</li>
</ul>



<p class="wp-block-paragraph"><strong>Cons</strong></p>



<ul class="wp-block-list">
<li>Need to manage the Cloud SQL Proxy installation and configuration in the Dockerfile.</li>



<li>All the cons described in the managed Cloud SQL Proxy solution above still apply here.</li>
</ul>



<h3 class="wp-block-heading" id="option-d-use-cloud-sq-ls-private-ip-and-client-certificate">Option D: Use Cloud SQL&#8217;s Private IP and Client Certificate</h3>



<p class="wp-block-paragraph">Instead of relying on external tools/libraries, the application code connects directly to the Cloud SQL instance using the private IP where the client certificate is also provided.</p>



<p class="wp-block-paragraph"><strong>Python App</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os

import pymysql

# ... simplified for brevity

pymysql.connect(
  host=os.getenv(&#039;DB_HOST&#039;), # Cloud SQL&#039;s Private IP
  database=os.getenv(&#039;DB_NAME&#039;),
  user=os.getenv(&#039;DB_USER&#039;),
  password=os.getenv(&#039;DB_PASS&#039;),
  ssl={
    &#039;ca&#039;: os.getenv(&#039;DB_SSL_SERVER_CA&#039;),     # /app/certs/ca/server-ca.pem
    &#039;cert&#039;: os.getenv(&#039;DB_SSL_CLIENT_CERT&#039;), # /app/certs/cert/client-cert.pem
    &#039;key&#039;: os.getenv(&#039;DB_SSL_CLIENT_KEY&#039;),   # /app/certs/key/client-key.pem
    &#039;check_hostname&#039;: False,
  }
)
</pre></div>


<p class="wp-block-paragraph"><strong>Note:</strong> The hostname checking must be disabled because the database host (Cloud SQL&#8217;s Private IP) will never<br>match the common name specified in the certificate. If the hostname checking is not disabled, it will produce the following error:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Error connecting to database: (2003, &#8220;Can&#8217;t connect to MySQL server on &#8217;10.x.x.x&#8217; ([SSL: CERTIFICATE_VERIFY_FAILED]<br>certificate verify failed: IP address mismatch, certificate is not valid for &#8217;10.x.x.x&#8217;. (_ssl.c:1000))&#8221;)</p>
</blockquote>



<p class="wp-block-paragraph"><strong>Pros</strong></p>



<ul class="wp-block-list">
<li>No additional wrappers like Cloud SQL Proxy or Cloud SQL Language Connectors are required.</li>
</ul>



<p class="wp-block-paragraph"><strong>Cons</strong></p>



<ul class="wp-block-list">
<li>Additional configurations and complexity on the IaC and application code are needed to manage and ingest the client certificate securely.</li>
</ul>



<h2 class="wp-block-heading" id="recommendations">Recommendations</h2>



<h3 class="wp-block-heading" id="best-solution-option-a">Best Solution &#8211; Option A</h3>



<p class="wp-block-paragraph">Use Cloud SQL Language Connectors if your app uses one of the supported programming languages (Python, Java, Go, or Node.js). </p>



<p class="wp-block-paragraph">It eliminates many unnecessary configurations on the IaC code and application code. In other words, there&#8217;s no need to do the following:</p>



<ul class="wp-block-list">
<li>Create a client certificate for Cloud SQL.</li>



<li>Store server certificate, client certificate, and client key in Secret Manager securely.</li>



<li>Mount files containing the server certificate, client certificate, and client key as volumes in the Cloud Run instance.</li>



<li>Pass certificate information to the application code via environment variables. </li>
</ul>



<p class="wp-block-paragraph">None of the above is needed. </p>



<p class="wp-block-paragraph">Less codebase = Less chance of errors.</p>



<h3 class="wp-block-heading" id="distance-next-best-solution-option-d">Distance Next Best Solution &#8211; Option D</h3>



<p class="wp-block-paragraph">Connect directly using the Cloud SQL instance&#8217;s Private IP and client certificate.</p>



<p class="wp-block-paragraph">It eliminates the need to rely on additional cloud configuration or external library (i.e., fewer points of failure). </p>



<p class="wp-block-paragraph">However, it introduces additional configurations in IaC and application code to maintain and ingest the certificate described above.</p>



<h3 class="wp-block-heading" id="least-favorite-solution-option-b-c">Least Favorite Solution &#8211; Option B &amp; C</h3>



<p class="wp-block-paragraph">While both Cloud SQL Proxy solutions (volume mount in the Cloud Run instance or installing it directly via Dockerfile) don&#8217;t require a client certificate, they come with <a href="https://cloud.google.com/sql/docs/mysql/sql-proxy#production-environment" target="_blank" rel="noopener">additional considerations and complexities when running in production</a>. </p>



<p class="wp-block-paragraph">Furthermore, the volume mount solution won&#8217;t work if the Cloud SQL instance has both private and public IPs. </p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/">Cloud Run: Connecting to Cloud SQL with Private IP and SSL</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2642</post-id>	</item>
		<item>
		<title>Supercharge Docker Build Pipeline by 97%</title>
		<link>https://myshittycode.com/2024/03/21/supercharge-docker-build-pipeline/</link>
					<comments>https://myshittycode.com/2024/03/21/supercharge-docker-build-pipeline/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Thu, 21 Mar 2024 14:26:02 +0000</pubDate>
				<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Artifact Registry]]></category>
		<category><![CDATA[Cloud Build]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Google Cloud Platform]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2568</guid>

					<description><![CDATA[<p>This tutorial shows how to improve the performance of the remote pipeline that builds a Docker image using docker build, which takes 15 minutes to 20 seconds. Note: Although Google&#8217;s Cloud Build is used here, this solution can be applied in GitHub Actions, Azure Pipeline, or any pipeline-driven Docker builds. The Challenge with Docker Build [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/03/21/supercharge-docker-build-pipeline/">Supercharge Docker Build Pipeline by 97%</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#the-challenge-with-docker-build">The Challenge with Docker Build</a></li><li><a href="#solution">Solution</a><ul><li><a href="#introducing-buildx">Introducing Buildx</a></li><li><a href="#step-1-enable-experimental-mode">Step 1: Enable experimental mode</a></li><li><a href="#step-2-specify-build-driver">Step 2: Specify build driver</a></li><li><a href="#step-3-configure-build-caches">Step 3: Configure build caches</a></li><li><a href="#step-4-putting-everything-together">Step 4: Putting everything together</a></li><li><a href="#step-5-analyze-build-results">Step 5: Analyze build results</a></li><li><a href="#step-6-verify-image-repository">Step 6: Verify image repository</a></li></ul></li></ul></nav></div>



<p class="wp-block-paragraph">This tutorial shows how to improve the performance of the remote pipeline that builds a Docker image using <strong>docker build</strong>, which takes 15 minutes to 20 seconds.</p>



<p class="wp-block-paragraph"><strong>Note:</strong> Although Google&#8217;s Cloud Build is used here, this solution can be applied in GitHub Actions, Azure Pipeline, or any pipeline-driven Docker builds.</p>



<h2 class="wp-block-heading" id="the-challenge-with-docker-build">The Challenge with Docker Build</h2>



<p class="wp-block-paragraph">Given the following Google&#8217;s Cloud Build script:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
# cloudbuild.yml

steps:
  - name: gcr.io/cloud-builders/docker
    args: &#x5B;&quot;build&quot;, &quot;-t&quot;, &quot;${_NAME}&quot;, &quot;.&quot;]

images:
  - ${_NAME}

substitutions:
  _LOCATION: us-central1
  _REPO: shared
  _IMAGE: chatbot
  _NAME: ${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}

options:
  logging: CLOUD_LOGGING_ONLY
  machineType: E2_HIGHCPU_32
  dynamicSubstitutions: true
</pre></div>


<p class="wp-block-paragraph">When kicking off the build&#8230;</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
gcloud builds submit --config=cloudbuild.yml --project=&#x5B;PROJECT ID]
</pre></div>


<p class="wp-block-paragraph">&#8230; Docker builds the image successfully and pushes it to the Artifact Registry.</p>



<p class="wp-block-paragraph">When rerunning the same build multiple times, it takes similar time to run:</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="192" src="https://myshittycode.com/wp-content/uploads/2024/03/docker-build-before-1-1024x192.png?x45560" alt="" class="wp-image-2570" srcset="https://myshittycode.com/wp-content/uploads/2024/03/docker-build-before-1-1024x192.png 1024w, https://myshittycode.com/wp-content/uploads/2024/03/docker-build-before-1-300x56.png 300w, https://myshittycode.com/wp-content/uploads/2024/03/docker-build-before-1-768x144.png 768w, https://myshittycode.com/wp-content/uploads/2024/03/docker-build-before-1.png 1346w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">In the example above, building the image with no changes to the Dockerfile takes an average of 15 minutes.</p>



<h2 class="wp-block-heading" id="solution">Solution</h2>



<p class="wp-block-paragraph">When running the build locally, Docker takes advantage of the layer caching by storing the data in the file system. This results in faster subsequent builds. </p>



<p class="wp-block-paragraph">However, when using a remote pipeline, the agent assigned to the pipeline job is ephemeral. Hence, Docker must always rebuild all layers, resulting in a long build time. To fix this annoyance, we must instruct Docker to cache the layers elsewhere. </p>



<h3 class="wp-block-heading" id="introducing-buildx">Introducing Buildx</h3>



<p class="wp-block-paragraph">Docker has a relatively unheard CLI command called Buildx that allows the layers to be cached remotely. <a href="https://docs.docker.com/build/architecture/" target="_blank" rel="noopener">Buildx has been the default build client</a> since Docker Engine 23.0 and Docker Desktop 4.19.</p>



<p class="wp-block-paragraph">To investigate, run the following commands locally to verify:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [5]; title: ; notranslate">
$ docker --version
Docker version 25.0.3, build 4debf41

$ docker build --help
Usage:  docker buildx build &#x5B;OPTIONS] PATH | URL | -

// TRUNCATED
</pre></div>


<h3 class="wp-block-heading" id="step-1-enable-experimental-mode">Step 1: Enable experimental mode</h3>



<p class="wp-block-paragraph">At the time of writing, Google&#8217;s Cloud Build uses a Docker version older than v23.0.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="180" src="https://myshittycode.com/wp-content/uploads/2024/03/cloudbuild-docker-version-1024x180.png?x45560" alt="" class="wp-image-2576" srcset="https://myshittycode.com/wp-content/uploads/2024/03/cloudbuild-docker-version-1024x180.png 1024w, https://myshittycode.com/wp-content/uploads/2024/03/cloudbuild-docker-version-300x53.png 300w, https://myshittycode.com/wp-content/uploads/2024/03/cloudbuild-docker-version-768x135.png 768w, https://myshittycode.com/wp-content/uploads/2024/03/cloudbuild-docker-version.png 1396w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Hence, an environment variable must be set to enable the experimental mode when using an older Docker version.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; highlight: [7,8]; title: ; notranslate">
# cloudbuild.yml

options:
  logging: CLOUD_LOGGING_ONLY
  machineType: E2_HIGHCPU_32
  dynamicSubstitutions: true
  env:
    - DOCKER_CLI_EXPERIMENTAL=enabled
</pre></div>


<h3 class="wp-block-heading" id="step-2-specify-build-driver">Step 2: Specify build driver</h3>



<p class="wp-block-paragraph">A different Docker driver must be explicitly specified to use the layer caching feature in the older Docker version.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
# cloudbuild.yml

steps:
  - name: gcr.io/cloud-builders/docker
    entrypoint: bash
    args:
      - -c
      - &gt;-
        docker buildx create
        --driver docker-container
        --use
</pre></div>


<h3 class="wp-block-heading" id="step-3-configure-build-caches">Step 3: Configure build caches</h3>



<p class="wp-block-paragraph">Now, configure Docker to fetch and store the caches.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
# cloudbuild.yml

steps:
  # ...

  - name: gcr.io/cloud-builders/docker
    entrypoint: bash
    args:
      - -c
      - &gt;-
        docker buildx build
        --cache-from ${_NAME}:cache
        --cache-to type=registry,ref=${_NAME}:cache,mode=max
        -t ${_NAME} .
        --push
</pre></div>


<p class="wp-block-paragraph">In this example, the cache image is stored in the same image repository in Artifact Registry but with a different tag, <strong>cache</strong>.</p>



<p class="wp-block-paragraph">To ensure all layers&#8217; build information is cached, <strong>mode=max</strong> is specified, too.</p>



<h3 class="wp-block-heading" id="step-4-putting-everything-together">Step 4: Putting everything together</h3>



<p class="wp-block-paragraph">The modified build file looks like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
# cloudbuild.yml

steps:
  - name: gcr.io/cloud-builders/docker
    entrypoint: bash
    args:
      - -c
      - &gt;-
        docker buildx create
        --driver docker-container
        --use

  - name: gcr.io/cloud-builders/docker
    entrypoint: bash
    args:
      - -c
      - &gt;-
        docker buildx build
        --cache-from ${_NAME}:cache
        --cache-to type=registry,ref=${_NAME}:cache,mode=max
        -t ${_NAME} .
        --push

substitutions:
  _LOCATION: us-central1
  _REPO: shared
  _IMAGE: chatbot
  _NAME: ${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}

options:
  logging: CLOUD_LOGGING_ONLY
  machineType: E2_HIGHCPU_32
  dynamicSubstitutions: true
  env:
    - DOCKER_CLI_EXPERIMENTAL=enabled
</pre></div>


<h3 class="wp-block-heading" id="step-5-analyze-build-results">Step 5: Analyze build results</h3>



<p class="wp-block-paragraph">Now, submit the build multiple times to examine the results.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="232" src="https://myshittycode.com/wp-content/uploads/2024/03/docker-build-after-1024x232.png?x45560" alt="" class="wp-image-2580" srcset="https://myshittycode.com/wp-content/uploads/2024/03/docker-build-after-1024x232.png 1024w, https://myshittycode.com/wp-content/uploads/2024/03/docker-build-after-300x68.png 300w, https://myshittycode.com/wp-content/uploads/2024/03/docker-build-after-768x174.png 768w, https://myshittycode.com/wp-content/uploads/2024/03/docker-build-after.png 1352w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">The build logs show that the first run takes about 12 minutes because the cache has yet to exist. Once the cache exists, the subsequent build takes an average of 20 seconds.</p>



<p class="wp-block-paragraph">The reason for the gap between 4 PM and 7 PM was that nobody had the time to sit for 12 minutes for the build to finish.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="543" height="460" src="https://myshittycode.com/wp-content/uploads/2024/03/beer.jpg?x45560" alt="" class="wp-image-2581" srcset="https://myshittycode.com/wp-content/uploads/2024/03/beer.jpg 543w, https://myshittycode.com/wp-content/uploads/2024/03/beer-300x254.jpg 300w" sizes="auto, (max-width: 543px) 100vw, 543px" /></figure>



<h3 class="wp-block-heading" id="step-6-verify-image-repository">Step 6: Verify image repository</h3>



<p class="wp-block-paragraph">The images stored in the Artifact Registry look like this:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="483" src="https://myshittycode.com/wp-content/uploads/2024/03/image-artifact-registry-1-1024x483.png?x45560" alt="" class="wp-image-2589" srcset="https://myshittycode.com/wp-content/uploads/2024/03/image-artifact-registry-1-1024x483.png 1024w, https://myshittycode.com/wp-content/uploads/2024/03/image-artifact-registry-1-300x142.png 300w, https://myshittycode.com/wp-content/uploads/2024/03/image-artifact-registry-1-768x363.png 768w, https://myshittycode.com/wp-content/uploads/2024/03/image-artifact-registry-1-1536x725.png 1536w, https://myshittycode.com/wp-content/uploads/2024/03/image-artifact-registry-1.png 1716w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">As you can see, the final image (with the <strong>latest</strong> tag) and the cache image (with the <strong>cache</strong> tag) are successfully stored in the Artifact Registry. The 20-second build is exclusively spent pulling a few gigabytes of cache image from the Artifact Registry.</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/03/21/supercharge-docker-build-pipeline/">Supercharge Docker Build Pipeline by 97%</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/03/21/supercharge-docker-build-pipeline/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2568</post-id>	</item>
		<item>
		<title>GCSFuse + Docker: &#8220;Error while creating mount source path &#8216;/a&#8217;: mkdir /a: file exists.&#8221;</title>
		<link>https://myshittycode.com/2022/03/02/gcsfuse-docker-error-while-creating-mount-source-path-a-mkdir-a-file-exists/</link>
					<comments>https://myshittycode.com/2022/03/02/gcsfuse-docker-error-while-creating-mount-source-path-a-mkdir-a-file-exists/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Thu, 03 Mar 2022 00:34:34 +0000</pubDate>
				<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Google Cloud Platform]]></category>
		<category><![CDATA[Google Cloud Storage]]></category>
		<category><![CDATA[Google Cloud Storage FUSE]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=1352</guid>

					<description><![CDATA[<p>This post illustrates how you can mount a GCS bucket using GCSFuse on your host machine and expose it as a volume to a Docker container. PROBLEM You want to volume mount a FUSE-mounted directory to a container, for example: When attempting to run the container&#8230; &#8230; an error occurred: SOLUTION Unmount the existing FUSE-mounted [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2022/03/02/gcsfuse-docker-error-while-creating-mount-source-path-a-mkdir-a-file-exists/">GCSFuse + Docker: &#8220;Error while creating mount source path &#8216;/a&#8217;: mkdir /a: file exists.&#8221;</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This post illustrates how you can mount a GCS bucket using GCSFuse on your host machine and expose it as a volume to a Docker container.</p>



<h2 class="wp-block-heading" id="problem">PROBLEM</h2>



<p class="wp-block-paragraph">You want to volume mount a <a href="https://github.com/GoogleCloudPlatform/gcsfuse" target="_blank" rel="noopener">FUSE</a>-mounted directory to a container, for example:</p>



<figure class="wp-block-image size-large"><a href="https://myshittycode.com/wp-content/uploads/2022/03/aif_-2022-page-10-4.png?x45560"><img loading="lazy" decoding="async" width="888" height="560" src="https://myshittycode.com/wp-content/uploads/2022/03/aif_-2022-page-10-4.png?x45560" alt="" class="wp-image-1361" srcset="https://myshittycode.com/wp-content/uploads/2022/03/aif_-2022-page-10-4.png 888w, https://myshittycode.com/wp-content/uploads/2022/03/aif_-2022-page-10-4-300x189.png 300w, https://myshittycode.com/wp-content/uploads/2022/03/aif_-2022-page-10-4-768x484.png 768w" sizes="auto, (max-width: 888px) 100vw, 888px" /></a></figure>



<p class="wp-block-paragraph">When attempting to run the container&#8230;</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
docker run -it --rm -v /my-kfc-bucket:/home busybox
</pre></div>


<p class="wp-block-paragraph">&#8230; an error occurred:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
docker: Error response from daemon: error while creating
mount source path &#039;/my-kfc-bucket&#039;: mkdir /my-kfc-bucket: 
file exists.
</pre></div>


<h2 class="wp-block-heading" id="solution">SOLUTION</h2>



<p class="wp-block-paragraph">Unmount the existing FUSE-mounted directory.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
sudo umount /my-kfc-bucket
</pre></div>


<p class="wp-block-paragraph">Mount it back with the following option. Because this command with <strong>-o allow_other</strong> must be executed with sudo privilege, you will need to change the root ownership to yourself (via <strong>&#8211;uid</strong> and <strong>&#8211;gid</strong>) so that you can easily read/write within the directory.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
sudo gcsfuse \
  -o allow_other \
  --uid $(id -u) \
  --gid $(id -g) \
  gcs-bucket /my-kfc-bucket  
</pre></div>


<p class="wp-block-paragraph">If it is successful, the output should look like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Start gcsfuse/0.40.0 (Go version go1.17.6) for app &quot;&quot; using mount point: /my-kfc-bucket
Opening GCS connection...
Mounting file system &quot;gcs-bucket&quot;...
File system has been successfully mounted.
</pre></div>


<p class="wp-block-paragraph">Rerun the docker container.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
docker run -it --rm -v /my-kfc-bucket:/home busybox
</pre></div>


<p class="wp-block-paragraph">Now, you can read/write the GCS bucket&#8217;s data from the container. In this example, the GCS bucket&#8217;s data is located in <strong>/home</strong>.</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2022/03/02/gcsfuse-docker-error-while-creating-mount-source-path-a-mkdir-a-file-exists/">GCSFuse + Docker: &#8220;Error while creating mount source path &#8216;/a&#8217;: mkdir /a: file exists.&#8221;</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2022/03/02/gcsfuse-docker-error-while-creating-mount-source-path-a-mkdir-a-file-exists/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1352</post-id>	</item>
		<item>
		<title>GCP + Container Registry: Pushing/Pulling Images</title>
		<link>https://myshittycode.com/2021/05/28/gcp-container-registry-pushing-pulling-images/</link>
					<comments>https://myshittycode.com/2021/05/28/gcp-container-registry-pushing-pulling-images/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Fri, 28 May 2021 18:58:35 +0000</pubDate>
				<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Google Cloud CLI]]></category>
		<category><![CDATA[Google Cloud Platform]]></category>
		<category><![CDATA[Google Container Registry]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=1239</guid>

					<description><![CDATA[<p>PROBLEM You want to push a new image to Google Container Registry (GCR) or pull an existing image from GCR. SOLUTION Pushing a New Image to GCR Prepare your Dockerfile. Create an image and tag it with a path pointing to GCR within a project. There are several variations of GCR&#8217;s hostname (ex: gcr.io, us.gcr.io, [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2021/05/28/gcp-container-registry-pushing-pulling-images/">GCP + Container Registry: Pushing/Pulling Images</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">PROBLEM</h2>



<p class="wp-block-paragraph">You want to push a new image to Google Container Registry (GCR) or pull an existing image from GCR.</p>



<h2 class="wp-block-heading">SOLUTION</h2>



<h3 class="wp-block-heading">Pushing a New Image to GCR</h3>



<p class="wp-block-paragraph">Prepare your Dockerfile.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
FROM alpine:3.7

# some content...
</pre></div>


<p class="wp-block-paragraph">Create an image and tag it with a path pointing to GCR within a project. </p>



<p class="wp-block-paragraph">There are several variations of GCR&#8217;s hostname (ex: <strong>gcr.io</strong>, <strong>us.gcr.io</strong>, <strong>eu.gcr.io</strong>, etc) depending on the data center&#8217;s location. </p>



<p class="wp-block-paragraph">The GCR path has the following format: <strong>[HOSTNAME]/[PROJECT-ID]/[IMAGE]</strong>.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
docker build -t gcr.io/shitty-project/shitty-repo .
</pre></div>


<p class="wp-block-paragraph">Log into GCP.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
gcloud auth login
</pre></div>


<p class="wp-block-paragraph">Register <strong>gcloud</strong> as a Docker credential helper.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
gcloud auth configure-docker
</pre></div>


<p class="wp-block-paragraph">Push the image to GCR.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
docker push gcr.io/shitty-project/shitty-repo
</pre></div>


<p class="wp-block-paragraph">View pushed image.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
gcloud container images list-tags gcr.io/shitty-project/shitty-repo

DIGEST        TAGS    TIMESTAMP
78b36c0b456d  latest  2019-03-07T16:19:53
</pre></div>


<p class="wp-block-paragraph">The repository and image can also be viewed in GCP Console.</p>



<figure class="wp-block-image size-large"><a href="https://myshittycode.com/wp-content/uploads/2021/05/gcr-1.png?x45560"><img loading="lazy" decoding="async" width="1874" height="586" src="https://myshittycode.com/wp-content/uploads/2021/05/gcr-1.png?x45560" alt="" class="wp-image-1248" srcset="https://myshittycode.com/wp-content/uploads/2021/05/gcr-1.png 1874w, https://myshittycode.com/wp-content/uploads/2021/05/gcr-1-300x94.png 300w, https://myshittycode.com/wp-content/uploads/2021/05/gcr-1-1024x320.png 1024w, https://myshittycode.com/wp-content/uploads/2021/05/gcr-1-768x240.png 768w, https://myshittycode.com/wp-content/uploads/2021/05/gcr-1-1536x480.png 1536w" sizes="auto, (max-width: 1874px) 100vw, 1874px" /></a><figcaption class="wp-element-caption">Image in GCR</figcaption></figure>



<h3 class="wp-block-heading">Pulling an Existing Image from GCR</h3>



<p class="wp-block-paragraph">Proceed with the authentication process if it is not done already.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
gcloud auth login
gcloud auth configure-docker
</pre></div>


<p class="wp-block-paragraph">Pull the image from GCR.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
docker pull gcr.io/shitty-project/shitty-repo
</pre></div><p>The post <a rel="nofollow" href="https://myshittycode.com/2021/05/28/gcp-container-registry-pushing-pulling-images/">GCP + Container Registry: Pushing/Pulling Images</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2021/05/28/gcp-container-registry-pushing-pulling-images/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1239</post-id>	</item>
		<item>
		<title>Docker: Executing Startup Script When Running Container Interactively</title>
		<link>https://myshittycode.com/2021/02/01/docker-executing-startup-script-when-running-container-interactively/</link>
					<comments>https://myshittycode.com/2021/02/01/docker-executing-startup-script-when-running-container-interactively/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Tue, 02 Feb 2021 03:34:38 +0000</pubDate>
				<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Docker]]></category>
		<guid isPermaLink="false">http://myshittycode.com/?p=1191</guid>

					<description><![CDATA[<p>PROBLEM When running the Docker container interactively (ex: docker run &#8211;rm -it myimage), you want to run a startup script every time. SOLUTION For Ubuntu, Debian and Centos images, write the startup script to /root/.bashrc: For Alpine image, it&#8217;s a little different because it uses Ash shell. Besides writing the startup script to /root/.profile, you [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2021/02/01/docker-executing-startup-script-when-running-container-interactively/">Docker: Executing Startup Script When Running Container Interactively</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">PROBLEM</h2>



<p class="wp-block-paragraph">When running the Docker container interactively (ex: <b>docker run &#8211;rm -it myimage</b>), you want to run a startup script every time.</p>



<h2 class="wp-block-heading">SOLUTION</h2>



<p class="wp-block-paragraph">For Ubuntu, Debian and Centos images, write the startup script to <b>/root/.bashrc</b>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
# UBUNTU
FROM ubuntu:latest
RUN echo &quot;echo &#039;Welcome!&#039;&quot; &amp;gt;&amp;gt; /root/.bashrc
WORKDIR /home

# DEBIAN
FROM debian:latest
RUN echo &quot;echo &#039;Welcome!&#039;&quot; &amp;gt;&amp;gt; /root/.bashrc
WORKDIR /home

# CENTOS
FROM centos:latest
RUN echo &quot;echo &#039;Welcome!&#039;&quot; &amp;gt;&amp;gt; /root/.bashrc
WORKDIR /home
</pre></div>


<p class="wp-block-paragraph">For Alpine image, it&#8217;s a little different because it uses Ash shell. Besides writing the startup script to <b>/root/.profile</b>, you also need to set that path to an environment variable called <b>ENV</b>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
FROM alpine:latest
ENV ENV=/root/.profile
RUN echo &quot;echo &#039;Welcome!&#039;&quot; &amp;gt; $ENV
WORKDIR /home
</pre></div><p>The post <a rel="nofollow" href="https://myshittycode.com/2021/02/01/docker-executing-startup-script-when-running-container-interactively/">Docker: Executing Startup Script When Running Container Interactively</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2021/02/01/docker-executing-startup-script-when-running-container-interactively/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1191</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Lazy Loading (feed)
Database Caching using Disk

Served from: myshittycode.com @ 2026-07-04 14:44:43 by W3 Total Cache
-->