<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Jonas Kaninda's TechBlog]]></title><description><![CDATA[Site Reliability/DevOps Engineer Consultant, Certified Cloud Engineer, and Kubernetes Administrator with +5 years of professional experience in Software development.]]></description><link>https://blog.jkaninda.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 14 May 2026 23:02:34 GMT</lastBuildDate><atom:link href="https://blog.jkaninda.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Goma Gateway – Modern, Lightweight API Gateway & Reverse Proxy]]></title><description><![CDATA[Goma Gateway - A Lightweight API Gateway & Reverse Proxy with Declarative Config and Powerful Middleware
Introduction
Goma Gateway is a high-performance, security-focused API Gateway built for modern developers and cloud-native environments.It combin...]]></description><link>https://blog.jkaninda.dev/goma-gateway-modern-lightweight-api-gateway-and-reverse-proxy</link><guid isPermaLink="true">https://blog.jkaninda.dev/goma-gateway-modern-lightweight-api-gateway-and-reverse-proxy</guid><category><![CDATA[Reverse Proxy]]></category><category><![CDATA[API Gateway]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Jonas Kaninda]]></dc:creator><pubDate>Sun, 10 Aug 2025 17:33:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754847070488/c410c5d7-0f51-4f55-ad3e-0c4843b8df39.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-goma-gateway-a-lightweight-api-gateway-amp-reverse-proxy-with-declarative-config-and-powerful-middleware"><strong>Goma Gateway - A Lightweight API Gateway &amp; Reverse Proxy with Declarative Config and Powerful Middleware</strong></h1>
<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p><strong>Goma Gateway</strong> is a high-performance, security-focused API Gateway built for modern developers and cloud-native environments.<br />It combines an intuitive declarative configuration system with powerful middleware and first-class observability, helping you <strong>route, secure, and scale traffic effortlessly</strong>.</p>
<p>Whether you’re managing microservices, securing public APIs, or modernizing legacy systems, Goma Gateway delivers <strong>flexibility without unnecessary complexity</strong>.</p>
<p><strong>Architecture:</strong></p>
<p><img src="https://raw.githubusercontent.com/jkaninda/goma-gateway/main/goma-gateway.png" alt="Goma architecture" /></p>
<h2 id="heading-core-capabilities">Core Capabilities</h2>
<h3 id="heading-routing-amp-traffic-management"><strong>Routing &amp; Traffic Management</strong></h3>
<ul>
<li><p>Declarative <strong>YAML-based configuration</strong></p>
</li>
<li><p>Flexible routing for <strong>domains, hosts, paths, WebSocket, gRPC, TCP/UDP</strong></p>
</li>
<li><p>Multi-domain &amp; multi-service support in one config</p>
</li>
<li><p>Reverse proxy with backend abstraction</p>
</li>
<li><p>Traffic control: <strong>rate limiting, load balancing, health checks</strong></p>
</li>
<li><p><strong>Canary Deployments</strong>: Safely roll out new versions of your services with advanced canary deployment strategies:</p>
<ul>
<li><p><strong>Weighted Backends</strong> – Gradually shift traffic between service versions using percentage-based routing.</p>
</li>
<li><p><strong>Conditional Routing</strong> – Route requests based on user groups, headers, query parameters, or cookies for targeted rollouts.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-security-amp-access-control"><strong>Security &amp; Access Control</strong></h3>
<ul>
<li><p>Automatic HTTPS via <strong>Let’s Encrypt</strong> or custom TLS</p>
</li>
<li><p><strong>Mutual TLS (mTLS)</strong> for client certificate authentication</p>
</li>
<li><p>Built-in authentication: <strong>Basic Auth, JWT, OAuth, LDAP, ForwardAuth</strong></p>
</li>
<li><p>CORS policies, header injection, fine-grained access control</p>
</li>
<li><p>Exploit protection: <strong>SQL injection, XSS</strong>, and bot detection</p>
</li>
<li><p>Method restrictions and regex-based URL rewriting</p>
</li>
</ul>
<h3 id="heading-performance-amp-reliability"><strong>Performance &amp; Reliability</strong></h3>
<ul>
<li><p><strong>HTTP caching</strong> (in-memory or Redis) with smart invalidation</p>
</li>
<li><p>Load balancing: round-robin, weighted, with health checks</p>
</li>
<li><p>Scalable rate limiting: local or Redis-based <em>(with automatic banning for repeated abuse)</em></p>
</li>
</ul>
<h3 id="heading-operations-amp-monitoring"><strong>Operations &amp; Monitoring</strong></h3>
<ul>
<li><p>Zero-downtime config reloads</p>
</li>
<li><p>Structured logging with configurable levels</p>
</li>
<li><p>Prometheus/Grafana metrics</p>
</li>
<li><p>Graceful error handling and backend failure interception</p>
</li>
</ul>
<h3 id="heading-cloud-native-integration"><strong>Cloud-Native Integration</strong></h3>
<ul>
<li><p>Kubernetes CRD support for native resource management</p>
</li>
<li><p>GitOps-friendly with version-controlled configs</p>
</li>
<li><p>Modular config files for organized route management</p>
</li>
<li><p>Horizontal scalability &amp; dynamic backend updates</p>
</li>
</ul>
<h2 id="heading-why-choose-goma-gateway"><strong>Why Choose Goma Gateway?</strong></h2>
<p>Goma Gateway isn’t just another reverse proxy; it’s <strong>built for modern workflows</strong>:</p>
<ul>
<li><p><strong>Simple, Declarative Configuration</strong>: Clean YAML config that’s easy to read, version-controlled, and automatable.</p>
</li>
<li><p><strong>First-Class Security:</strong> TLS, authentication, exploit protection, and access control baked in.</p>
</li>
<li><p><strong>Live Reload &amp; GitOps-Ready</strong>: Perfect for CI/CD pipelines.</p>
</li>
<li><p><strong>Observability from Day One</strong>: Logging and metrics built in.</p>
</li>
<li><p><strong>Cloud-Native</strong>: Seamless integration with Kubernetes.</p>
</li>
</ul>
<p><strong>Perfect for:</strong> Public APIs, internal microservices, legacy modernization, or any project requiring secure, scalable traffic management.</p>
<h2 id="heading-quick-tutorial-deploying-goma-gateway-as-a-reverse-proxy-for-docker-services"><strong>Quick Tutorial: Deploying Goma Gateway as a Reverse Proxy for Docker Services</strong></h2>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Make sure you have:</p>
<ul>
<li><p><strong>Docker</strong> installed</p>
</li>
<li><p>Basic knowledge of YAML config</p>
</li>
</ul>
<h3 id="heading-step-1-generate-a-default-config"><strong>Step 1. Generate a Default Config</strong></h3>
<pre><code class="lang-bash">docker run --rm --name goma-gateway \
  -v <span class="hljs-string">"<span class="hljs-variable">${PWD}</span>/config:/etc/goma/"</span> \
  jkaninda/goma-gateway config init --output /etc/goma/config.yml
</code></pre>
<p>This creates <code>config/config.yml</code> with default settings.</p>
<h3 id="heading-step-2-customize-your-configuration"><strong>Step 2. Customize Your Configuration</strong></h3>
<p>Example <code>config.yml</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-number">2</span>
<span class="hljs-attr">gateway:</span>
  <span class="hljs-attr">entryPoints:</span>
    <span class="hljs-attr">web:</span>
      <span class="hljs-attr">address:</span> <span class="hljs-string">":80"</span>
    <span class="hljs-attr">webSecure:</span>
      <span class="hljs-attr">address:</span> <span class="hljs-string">":443"</span>
  <span class="hljs-attr">routes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">api</span>
      <span class="hljs-attr">hosts:</span> [<span class="hljs-string">"api.example.com"</span>]
      <span class="hljs-attr">target:</span> <span class="hljs-string">http://okapi-api:8080</span>
      <span class="hljs-attr">middlewares:</span> [<span class="hljs-string">"basic-auth"</span>, <span class="hljs-string">"rate-limit"</span>]

<span class="hljs-attr">middlewares:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">basic-auth</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">basicAuth</span>
    <span class="hljs-attr">rule:</span>
      <span class="hljs-attr">users:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">username:</span> <span class="hljs-string">admin</span>
          <span class="hljs-attr">password:</span> <span class="hljs-string">$2y$05$TIx7l8sJWvMFXw4n0GbkQuOhemPQOormacQC4W1p28TOVzJtx.XpO</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">rate-limit</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">rateLimit</span>
    <span class="hljs-attr">rule:</span>
      <span class="hljs-attr">unit:</span> <span class="hljs-string">minute</span>
      <span class="hljs-attr">requestsPerUnit:</span> <span class="hljs-number">60</span>

<span class="hljs-attr">certManager:</span>
  <span class="hljs-attr">acme:</span>
    <span class="hljs-attr">email:</span> <span class="hljs-string">admin@example.com</span>
    <span class="hljs-attr">storageFile:</span> <span class="hljs-string">/etc/letsencrypt/acme.json</span>
</code></pre>
<h3 id="heading-step-3-run-with-docker-compose"><strong>Step 3. Run with Docker Compose</strong></h3>
<p><code>compose.yaml</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">goma-gateway:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/goma-gateway</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">-c</span> <span class="hljs-string">config.yaml</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"80:80"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"443:443"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./config:/etc/goma/</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./letsencrypt:/etc/letsencrypt</span>

  <span class="hljs-attr">okapi-api:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/okapi-example</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
</code></pre>
<p><strong>Your gateway is now live</strong>, ready to route, secure, and monitor your services.</p>
<h3 id="heading-step-4-grafana-dashboard"><strong>Step 4. Grafana Dashboard</strong></h3>
<p>Goma Gateway includes built-in monitoring to track the <strong>health, performance, and behavior</strong> of your gateway and its routes.<br />Metrics are exposed in a <strong>Prometheus-compatible</strong> format, making them easy to integrate with <strong>Prometheus</strong> and visualize using <strong>Grafana</strong>.</p>
<p>A <strong>prebuilt Grafana dashboard</strong> is available for quick setup and visualization.<br />You can import it directly using:</p>
<p><strong>Dashboard ID:</strong> <code>23799</code></p>
<p>Enable metrics</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-number">2</span>
<span class="hljs-attr">gateway:</span>
  <span class="hljs-comment"># Timeout settings (in seconds)</span>
  <span class="hljs-attr">timeouts:</span>
    <span class="hljs-attr">write:</span> <span class="hljs-number">30</span>
    <span class="hljs-attr">read:</span> <span class="hljs-number">30</span>
    <span class="hljs-attr">idle:</span> <span class="hljs-number">30</span>
  <span class="hljs-comment"># Optional, default port 8080</span>
  <span class="hljs-attr">entryPoints:</span>
    <span class="hljs-attr">web:</span>
      <span class="hljs-attr">address:</span> <span class="hljs-string">":80"</span>
    <span class="hljs-attr">webSecure:</span>
      <span class="hljs-attr">address:</span> <span class="hljs-string">":443"</span>
  <span class="hljs-attr">monitoring:</span>
    <span class="hljs-attr">enableMetrics:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">metricsPath:</span> <span class="hljs-string">/metrics</span>
  <span class="hljs-attr">extraConfig:</span>
    <span class="hljs-attr">directory:</span> <span class="hljs-string">/etc/goma/extra</span> <span class="hljs-comment"># Directory containing extra config files</span>
    <span class="hljs-attr">watch:</span> <span class="hljs-literal">false</span> <span class="hljs-comment"># Watch for changes in extra config directory and reload after changes</span>
</code></pre>
<h4 id="heading-dashboard-preview">Dashboard Preview</h4>
<p><img src="https://raw.githubusercontent.com/jkaninda/goma-gateway/main/docs/images/goma_gateway_observability_dashboard-23799.png" alt="Goma Gateway Grafana Dashboard" /></p>
<h2 id="heading-links"><strong>Links</strong></h2>
<ul>
<li><p><strong>GitHub</strong>: <a target="_blank" href="https://github.com/jkaninda/goma-gateway">jkaninda/goma-gateway</a></p>
</li>
<li><p><strong>Docker Hub</strong>: <a target="_blank" href="https://hub.docker.com/r/jkaninda/goma-gateway">jkaninda/goma-gateway</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Securing Docker Registry with LDAP Authentication Using Goma Gateway]]></title><description><![CDATA[Introduction
Securing a self-hosted Docker Registry is crucial, especially in production environments. LDAP (Lightweight Directory Access Protocol) is a widely adopted standard for managing user authentication and access control across services.
In t...]]></description><link>https://blog.jkaninda.dev/securing-docker-registry-with-ldap-authentication-using-goma-gateway</link><guid isPermaLink="true">https://blog.jkaninda.dev/securing-docker-registry-with-ldap-authentication-using-goma-gateway</guid><category><![CDATA[Goma Gateway]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Reverse Proxy]]></category><category><![CDATA[registry]]></category><category><![CDATA[docker-registry]]></category><dc:creator><![CDATA[Jonas Kaninda]]></dc:creator><pubDate>Tue, 05 Aug 2025 05:18:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754370654418/40f0d7c1-1fe1-4342-92da-fba711e84896.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Securing a self-hosted Docker Registry is crucial, especially in production environments. LDAP (Lightweight Directory Access Protocol) is a widely adopted standard for managing user authentication and access control across services.</p>
<p>In this tutorial, we’ll use <strong>Goma Gateway</strong> to secure a Docker Registry using <strong>LLDAP</strong> for authentication. Goma makes it easy to protect routes with built-in middleware and declarative configuration.</p>
<hr />
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>Docker and Docker Compose installed</p>
</li>
<li><p>Basic understanding of:</p>
<ul>
<li><p>Docker networking</p>
</li>
<li><p>Reverse proxies</p>
</li>
<li><p>LDAP concepts</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-architecture-overview"><strong>Architecture Overview</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754370336699/66c0b61c-2774-43f8-9c55-e269b1791360.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-1-what-is-goma-gateway">1. What Is Goma Gateway?</h2>
<p><strong>Goma Gateway</strong> is a lightweight, high-performance, and security-first API Gateway built for modern cloud-native infrastructure. With a developer-friendly configuration, Goma supports powerful middleware, observability, and advanced protocols out of the box.</p>
<h3 id="heading-key-features">Key Features</h3>
<ul>
<li><p>Built-in authentication: Basic, JWT, OAuth2, LDAP, and ForwardAuth</p>
</li>
<li><p>Rate limiting, HTTP caching, and bot detection</p>
</li>
<li><p>Observability: Prometheus metrics, route health checks</p>
</li>
<li><p>Protocol support: REST, gRPC, TCP, UDP</p>
</li>
<li><p>TLS support: Let’s Encrypt and custom certificates</p>
</li>
</ul>
<p>GitHub: <a target="_blank" href="https://github.com/jkaninda/goma-gateway">jkaninda/goma-gateway</a></p>
<h2 id="heading-2-what-is-lldap">2. What Is LLDAP?</h2>
<p><strong>LLDAP</strong> is a lightweight, opinionated LDAP server designed to simplify authentication. It’s easy to deploy and comes with a minimal web UI for managing users and groups.</p>
<p>GitHub: <a target="_blank" href="https://github.com/lldap/lldap">lldap/lldap</a></p>
<h2 id="heading-3-configure-local-hosts">3. Configure Local Hosts</h2>
<p>Edit your <code>/etc/hosts</code> file to define virtual domains:</p>
<pre><code class="lang-bash">127.0.0.1 lldap.example.com registry.example.com okapi-api.example.com
</code></pre>
<p>Replace <a target="_blank" href="http://example.com"><code>example.com</code></a> With your domain, if needed.</p>
<h2 id="heading-4-create-a-docker-network">4. Create a Docker Network</h2>
<p>Create a shared Docker network for all services:</p>
<pre><code class="lang-bash">docker network create web
</code></pre>
<h2 id="heading-5-docker-compose-setup">5. Docker Compose Setup</h2>
<p>Create a <code>compose.yaml</code> file with the following services:</p>
<ul>
<li><p><code>gateway</code>: Goma Gateway</p>
</li>
<li><p><code>lldap</code>: LDAP server</p>
</li>
<li><p><code>registry</code>: Docker Registry</p>
</li>
<li><p><code>okapi-api</code>: A test API service</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">gateway:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/goma-gateway</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">gateway</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./goma.yml:/etc/goma/goma.yml</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./extra:/etc/goma/extra</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./certs:/etc/goma/certs</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">80</span><span class="hljs-string">:80</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">443</span><span class="hljs-string">:443</span>
    <span class="hljs-attr">networks:</span> [<span class="hljs-string">web</span>]

  <span class="hljs-attr">lldap:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">lldap/lldap:stable</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">lldap</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">UID=1</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">GID=1</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TZ=Europe/Paris</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">LLDAP_JWT_SECRET=REPLACE_WITH_RANDOM</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">LLDAP_KEY_SEED=REPLACE_WITH_RANDOM</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">LLDAP_LDAP_BASE_DN=dc=example,dc=com</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">LLDAP_LDAP_USER_PASS=adminPasword</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3890:3890"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">data:/data</span>
    <span class="hljs-attr">networks:</span> [<span class="hljs-string">web</span>]

  <span class="hljs-attr">registry:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">registry:3.0.0</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">registry</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">registry:/var/lib/registry</span>
    <span class="hljs-attr">networks:</span> [<span class="hljs-string">web</span>]

  <span class="hljs-attr">okapi-api:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/okapi-example</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">okapi-api</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">networks:</span> [<span class="hljs-string">web</span>]

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">data:</span> {}
  <span class="hljs-attr">registry:</span> {}

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">web:</span>
    <span class="hljs-attr">external:</span> <span class="hljs-literal">true</span>
</code></pre>
<p><strong>Important Security Notes</strong>:</p>
<ol>
<li><p>Replace <code>LLDAP_JWT_SECRET</code> and <code>LLDAP_KEY_SEED</code> with strong random values</p>
</li>
<li><p>Change default <code>LLDAP_LDAP_USER_PASS</code></p>
</li>
</ol>
<h2 id="heading-6-goma-gateway-configuration">6. Goma Gateway Configuration</h2>
<p>Create a file named <code>goma.yml</code> in the project root:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-number">2</span>
<span class="hljs-attr">gateway:</span>
  <span class="hljs-attr">entryPoints:</span>
    <span class="hljs-attr">web:</span>
      <span class="hljs-attr">address:</span> <span class="hljs-string">"[::]:80"</span>
    <span class="hljs-attr">webSecure:</span>
      <span class="hljs-attr">address:</span> <span class="hljs-string">"[::]:443"</span>
  <span class="hljs-attr">tls:</span>
    <span class="hljs-attr">keys:</span> []
    <span class="hljs-comment"># Uncomment for custom certs:</span>
    <span class="hljs-comment"># - cert: /etc/goma/certs/cert.crt</span>
    <span class="hljs-comment">#   key: /etc/goma/certs/server.key.pem</span>
  <span class="hljs-attr">log:</span>
    <span class="hljs-attr">level:</span> <span class="hljs-string">info</span>
  <span class="hljs-attr">networking:</span>
    <span class="hljs-attr">transport:</span>
      <span class="hljs-attr">insecureSkipVerify:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">monitoring:</span>
    <span class="hljs-attr">enableMetrics:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">metricsPath:</span> <span class="hljs-string">/metrics</span>
    <span class="hljs-attr">enableLiveness:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">enableRouteHealthCheck:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">includeRouteHealthErrors:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">extraConfig:</span>
    <span class="hljs-attr">directory:</span> <span class="hljs-string">/etc/goma/extra</span>
    <span class="hljs-attr">watch:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">certManager:</span>
  <span class="hljs-attr">provider:</span> <span class="hljs-string">acme</span>
  <span class="hljs-attr">acme:</span>
    <span class="hljs-comment"># Uncomment to enable Let’s Encrypt</span>
    <span class="hljs-comment"># email: admin@example.com</span>
    <span class="hljs-attr">storageFile:</span> <span class="hljs-string">/etc/letsencrypt/acme.json</span>
</code></pre>
<h2 id="heading-7-define-routes-and-middlewares">7. Define Routes and Middlewares</h2>
<p>Create a folder named <code>extra</code> and add two files: <code>routes.yml</code> and <code>middlewares.yml</code>.</p>
<blockquote>
<p><code>extra/routes.yml</code></p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">routes:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">lldap</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
    <span class="hljs-attr">hosts:</span> [<span class="hljs-string">lldap.example.com</span>]
    <span class="hljs-attr">target:</span> <span class="hljs-string">http://lldap:17170</span>
    <span class="hljs-attr">middlewares:</span> [<span class="hljs-string">enforceHttps</span>]

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">registry</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
    <span class="hljs-attr">hosts:</span> [<span class="hljs-string">registry.example.com</span>]
    <span class="hljs-attr">rewrite:</span> <span class="hljs-string">''</span>
    <span class="hljs-attr">target:</span> <span class="hljs-string">http://registry:5000</span>
    <span class="hljs-attr">middlewares:</span> [<span class="hljs-string">enforceHttps</span>]

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">okapi-api</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
    <span class="hljs-attr">hosts:</span> [<span class="hljs-string">okapi-api.example.com</span>]
    <span class="hljs-attr">rewrite:</span> <span class="hljs-string">''</span>
    <span class="hljs-attr">target:</span> <span class="hljs-string">http://okapi-api:8080</span>
    <span class="hljs-attr">middlewares:</span> [<span class="hljs-string">enforceHttps</span>]
</code></pre>
<blockquote>
<p><code>extra/middlewares.yml</code></p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">middlewares:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">enforceHttps</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">redirectScheme</span>
    <span class="hljs-attr">rule:</span>
      <span class="hljs-attr">scheme:</span> <span class="hljs-string">https</span>
      <span class="hljs-attr">permanent:</span> <span class="hljs-literal">true</span>
</code></pre>
<h2 id="heading-8-deployment">8. Deployment</h2>
<p>Launch all services:</p>
<pre><code class="lang-bash">docker compose up -d
</code></pre>
<h2 id="heading-9-insecure-registry-configuration">9. Insecure Registry Configuration</h2>
<p>If you haven't configured TLS yet, Docker needs to allow connections to the insecure registry (development only):</p>
<p>Create or edit <code>/etc/docker/daemon.json</code>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"insecure-registries"</span>: [<span class="hljs-string">"registry.example.com"</span>]
}
</code></pre>
<p>Restart Docker:</p>
<pre><code class="lang-bash">sudo systemctl restart docker
</code></pre>
<p><strong>Tip:</strong> Use Let’s Encrypt or a custom certificate for production environments.</p>
<h2 id="heading-10-access-your-services">10. Access Your Services</h2>
<ul>
<li><p><strong>LDAP UI</strong>: <a target="_blank" href="https://lldap.example.com">https://lldap.example.com</a> Login with <code>admin / adminPasword</code>. Then:</p>
<ul>
<li><p>Create a group: <code>registry_users</code></p>
</li>
<li><p>Create a user: <code>bind_user</code> (add to <code>lldap_strict_readonly</code>)</p>
</li>
<li><p>Create another user and assign them to <code>registry_users</code></p>
</li>
</ul>
</li>
<li><p><strong>Okapi API</strong>: <a target="_blank" href="https://okapi-api.example.com/api/books">https://okapi-api.example.com/api/books</a></p>
</li>
<li><p><strong>Docker Registry API</strong>: <a target="_blank" href="https://registry.example.com/v2/_catalog">https://registry.example.com/v2/_catalog</a></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754370611283/f1c43b24-6521-48bc-be5f-294d8a01915e.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-11-enable-ldap-authentication">11. Enable LDAP Authentication</h2>
<p>Update <code>middlewares.yml</code> with LDAP auth middleware:</p>
<pre><code class="lang-yaml">  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ldap-auth</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">ldap</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/.*</span>
    <span class="hljs-attr">rule:</span>
      <span class="hljs-attr">forwardUsername:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">realm:</span> <span class="hljs-string">restricted</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">ldap://lldap:3890</span>
      <span class="hljs-attr">baseDN:</span> <span class="hljs-string">dc=example,dc=com</span>
      <span class="hljs-attr">bindDN:</span> <span class="hljs-string">uid=bind_user,ou=people,dc=example,dc=com</span>
      <span class="hljs-attr">bindPass:</span> <span class="hljs-string">bind_user_password</span>
      <span class="hljs-attr">userFilter:</span> <span class="hljs-string">"(&amp;(objectclass=person)(memberof=cn=registry_users,ou=groups,dc=example,dc=com)(uid=%s))"</span>
      <span class="hljs-attr">startTLS:</span> <span class="hljs-literal">false</span>
      <span class="hljs-attr">insecureSkipVerify:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">connPool:</span>
        <span class="hljs-attr">size:</span> <span class="hljs-number">150</span>
        <span class="hljs-attr">burst:</span> <span class="hljs-number">100</span>
        <span class="hljs-attr">ttl:</span> <span class="hljs-string">30s</span>
</code></pre>
<p>Then apply it in <code>routes.yml</code>:</p>
<pre><code class="lang-yaml">  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">registry</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
    <span class="hljs-attr">hosts:</span> [<span class="hljs-string">registry.example.com</span>]
    <span class="hljs-attr">rewrite:</span> <span class="hljs-string">''</span>
    <span class="hljs-attr">target:</span> <span class="hljs-string">http://registry:5000</span>
    <span class="hljs-attr">middlewares:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">enforceHttps</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">ldap-auth</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">okapi-api</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
    <span class="hljs-attr">hosts:</span> [<span class="hljs-string">okapi-api.example.com</span>]
    <span class="hljs-attr">rewrite:</span> <span class="hljs-string">''</span>
    <span class="hljs-attr">target:</span> <span class="hljs-string">http://okapi-api:8080</span>
    <span class="hljs-attr">middlewares:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">enforceHttps</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">ldap-auth</span>
</code></pre>
<hr />
<h2 id="heading-12-test-the-registry">12. Test the Registry</h2>
<h3 id="heading-pull-the-nginx-image">Pull the Nginx image:</h3>
<pre><code class="lang-bash">docker pull nginx:latest
</code></pre>
<h3 id="heading-tag-the-image">Tag the image:</h3>
<pre><code class="lang-bash">docker tag nginx:latest registry.example.com/nginx:latest
</code></pre>
<h3 id="heading-push-to-your-registry">Push to your registry:</h3>
<pre><code class="lang-bash">docker push registry.example.com/nginx:latest
</code></pre>
<p>You should be prompted for credentials, and only LDAP-authorized users will be able to push or pull.</p>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>You’ve successfully secured your Docker Registry using <strong>Goma Gateway</strong> and <strong>LLDAP</strong>. With just a few declarative configs and containers, you now have a robust, LDAP-backed authentication layer for your registry and a reusable pattern for protecting any internal API or service.</p>
<p>Feel free to extend this setup to protect other services and APIs with Goma’s flexible middleware system.</p>
]]></content:encoded></item><item><title><![CDATA[Creating REST APIs in Golang: A Practical Guide with Okapi Framework]]></title><description><![CDATA[REST (Representational State Transfer) APIs have become the backbone of modern web applications, enabling efficient communication and data exchange across systems.
Go (or Golang) is increasingly favored for backend development due to its performance,...]]></description><link>https://blog.jkaninda.dev/creating-rest-apis-in-golang-a-practical-guide-with-okapi-framework</link><guid isPermaLink="true">https://blog.jkaninda.dev/creating-rest-apis-in-golang-a-practical-guide-with-okapi-framework</guid><category><![CDATA[golang]]></category><category><![CDATA[APIs]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Golang developer]]></category><category><![CDATA[Golang Application Development]]></category><category><![CDATA[Go Language]]></category><dc:creator><![CDATA[Jonas Kaninda]]></dc:creator><pubDate>Mon, 23 Jun 2025 08:42:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750674067084/66d66dd9-96cd-4f2b-8847-9546162e6c92.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>REST (Representational State Transfer) APIs have become the backbone of modern web applications, enabling efficient communication and data exchange across systems.</p>
<p>Go (or Golang) is increasingly favored for backend development due to its performance, simplicity, and concurrency features.</p>
<p>In this tutorial, we’ll walk through building a REST API using the <a target="_blank" href="https://github.com/jkaninda/okapi">Okapi framework</a>—a modern, minimalist web framework for Go that emphasizes developer experience and clean API design.</p>
<h2 id="heading-what-is-okapi">What is Okapi?</h2>
<p>Okapi is a lightweight, performant web framework inspired by FastAPI. It is designed to help developers build scalable, well-documented APIs quickly, with minimal boilerplate.</p>
<p>Key features:</p>
<ul>
<li><p><strong>Minimalist syntax</strong>: Clean and declarative route definitions.</p>
</li>
<li><p><strong>OpenAPI integration</strong>: Automatic and real-time OpenAPI spec generation.</p>
</li>
<li><p><strong>Extensible middleware</strong>: Support for custom and built-in middleware like JWTAuth.</p>
</li>
</ul>
<h2 id="heading-why-use-okapi">Why use Okapi?</h2>
<ul>
<li><p><strong>Easy to Learn</strong> – With familiar Go syntax and intuitive APIs, you can be productive in minutes, even on your first project.</p>
</li>
<li><p><strong>Lightweight and Unopinionated</strong> – Okapi is built from the ground up and doesn’t wrap or build on top of another framework. It gives you full control without unnecessary abstraction or bloat.</p>
</li>
<li><p><strong>Highly Flexible</strong> – Designed to adapt to your architecture and workflow, not the other way around.</p>
</li>
<li><p><strong>Built for Production</strong> – Fast, reliable, and efficient under real-world load. Okapi is optimized for performance without sacrificing developer experience.</p>
</li>
<li><p><strong>Standard Library Compatibility</strong> - Integrates seamlessly with Go’s net/http standard library, making it easy to combine Okapi with existing Go code and tools.</p>
</li>
<li><p><strong>Automatic OpenAPI Documentation</strong> - Generate comprehensive OpenAPI specs automatically for every route, keeping your API documentation always up to date with your code.</p>
</li>
<li><p><strong>Dynamic Route Management</strong> - Enable or disable routes and route groups at runtime. No need to comment out code—just toggle behavior cleanly and efficiently.</p>
</li>
</ul>
<p><strong>Ideal for:</strong></p>
<ul>
<li><p><strong>High-performance REST APIs</strong></p>
</li>
<li><p><strong>Composable microservices</strong></p>
</li>
<li><p><strong>Rapid prototyping</strong></p>
</li>
<li><p><strong>Learning &amp; teaching Go web development</strong></p>
</li>
</ul>
<p><em>Whether you're building your next startup, internal tools, or side projects,</em> <strong><em>Okapi scales with you.</em></strong></p>
<hr />
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you begin, ensure you have the following installed:</p>
<ul>
<li><p>Basic knowledge of Go</p>
</li>
<li><p>Go installed (v1.24+ recommended)</p>
</li>
<li><p>A code editor (VS Code or similar)</p>
</li>
</ul>
<hr />
<h2 id="heading-project-setup">Project Setup</h2>
<p>Start by creating your project directory and initializing Go modules:</p>
<pre><code class="lang-bash">mkdir okapi-example &amp;&amp; <span class="hljs-built_in">cd</span> okapi-example
go mod init okapi-example
go get github.com/jkaninda/okapi@latest
</code></pre>
<p>Project folder structure:</p>
<pre><code class="lang-yaml"><span class="hljs-string">okapi-example/</span>
<span class="hljs-string">├──</span> <span class="hljs-string">controllers</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">controller.go</span>
<span class="hljs-string">├──</span> <span class="hljs-string">go.mod</span>
<span class="hljs-string">├──</span> <span class="hljs-string">go.sum</span>
<span class="hljs-string">├──</span> <span class="hljs-string">main.go</span>
<span class="hljs-string">├──</span> <span class="hljs-string">middlewares</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">middleware.go</span>
<span class="hljs-string">├──</span> <span class="hljs-string">models</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">model.go</span>
<span class="hljs-string">└──</span> <span class="hljs-string">routes</span>
    <span class="hljs-string">└──</span> <span class="hljs-string">route.go</span>
</code></pre>
<hr />
<h2 id="heading-basic-routing-example">Basic Routing Example</h2>
<p>Here’s a minimal Okapi server that responds with a welcome message:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"github.com/jkaninda/okapi"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    o := okapi.Default()

    o.Get(<span class="hljs-string">"/"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
        <span class="hljs-keyword">return</span> c.OK(okapi.M{<span class="hljs-string">"message"</span>: <span class="hljs-string">"Hello from Okapi Web Framework!"</span>, <span class="hljs-string">"License"</span>: <span class="hljs-string">"MIT"</span>})
    })

    o.Get(<span class="hljs-string">"/greet/:name"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
        name := c.Param(<span class="hljs-string">"name"</span>)
        <span class="hljs-keyword">return</span> c.OK(okapi.M{<span class="hljs-string">"message"</span>: <span class="hljs-string">"Hello, "</span> + name + <span class="hljs-string">"!"</span>})
    })

    <span class="hljs-keyword">if</span> err := o.Start(); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }
}
</code></pre>
<p>Run it with:</p>
<pre><code class="lang-bash">go run main.go
</code></pre>
<p>Visit <a target="_blank" href="http://localhost:8080/">http://localhost:8080</a> to see the response.</p>
<hr />
<h2 id="heading-building-a-book-api">Building a Book API</h2>
<p>We’ll now create a simple API to manage books, including authentication for admin-level routes.</p>
<hr />
<h3 id="heading-1-define-models">1. Define Models</h3>
<p><code>models/model.go</code>:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> models

<span class="hljs-keyword">type</span> Response <span class="hljs-keyword">struct</span> {
    Success <span class="hljs-keyword">bool</span>   <span class="hljs-string">`json:"success"`</span>
    Message <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"message"`</span>
    Data    Book   <span class="hljs-string">`json:"data"`</span>
}
<span class="hljs-keyword">type</span> Book <span class="hljs-keyword">struct</span> {
    Id    <span class="hljs-keyword">int</span>    <span class="hljs-string">`json:"id"`</span>
    Name  <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"name" form:"name"  max:"50" required:"true" description:"Book name"`</span>
    Price <span class="hljs-keyword">int</span>    <span class="hljs-string">`json:"price" form:"price" query:"price" yaml:"price" required:"true" description:"Book price"`</span>
}
<span class="hljs-keyword">type</span> ErrorResponse <span class="hljs-keyword">struct</span> {
    Success <span class="hljs-keyword">bool</span> <span class="hljs-string">`json:"success"`</span>
    Status  <span class="hljs-keyword">int</span>  <span class="hljs-string">`json:"status"`</span>
    Details any  <span class="hljs-string">`json:"details"`</span>
}

<span class="hljs-keyword">type</span> AuthRequest <span class="hljs-keyword">struct</span> {
    Username <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"username" required:"true" description:"Username for authentication"`</span>
    Password <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"password" required:"true" description:"Password for authentication"`</span>
}
<span class="hljs-keyword">type</span> AuthResponse <span class="hljs-keyword">struct</span> {
    Success   <span class="hljs-keyword">bool</span>   <span class="hljs-string">`json:"success"`</span>
    Message   <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"message"`</span>
    Token     <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"token,omitempty"`</span>
    ExpiresAt <span class="hljs-keyword">int64</span>  <span class="hljs-string">`json:"expires,omitempty"`</span>
}
<span class="hljs-keyword">type</span> UserInfo <span class="hljs-keyword">struct</span> {
    Name  <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"name"`</span>
    Email <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"email"`</span>
    Role  <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"role"`</span>
}
</code></pre>
<hr />
<h3 id="heading-2-implement-middleware-jwt">2. Implement Middleware (JWT)</h3>
<p><code>middlewares/middleware.go</code>:</p>
<blockquote>
<p>Uses Okapi's built-in JWTAuth with claim validation and token generation.</p>
</blockquote>
<pre><code class="lang-go">
<span class="hljs-keyword">package</span> middlewares

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"errors"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"github.com/golang-jwt/jwt/v5"</span>
    <span class="hljs-string">"github.com/jkaninda/okapi"</span>
    <span class="hljs-string">"github.com/jkaninda/okapi/examples/route-definition/models"</span>
    <span class="hljs-string">"log/slog"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"strings"</span>
    <span class="hljs-string">"time"</span>
)

<span class="hljs-keyword">var</span> (
    <span class="hljs-comment">// signingSecret is used to sign the JWT tokens</span>
    signingSecret = <span class="hljs-string">"supersecret"</span>

    JWTAuth = &amp;okapi.JWTAuth{
        SigningSecret:    []<span class="hljs-keyword">byte</span>(signingSecret),
        TokenLookup:      <span class="hljs-string">"header:Authorization"</span>,
        ClaimsExpression: <span class="hljs-string">"Equals(`email_verified`, `true`) &amp;&amp; Equals(`user.role`, `admin`) &amp;&amp; Contains(`permissions`, `create`, `delete`, `update`)"</span>,
        ForwardClaims: <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>{
            <span class="hljs-string">"email"</span>: <span class="hljs-string">"user.email"</span>,
            <span class="hljs-string">"role"</span>:  <span class="hljs-string">"user.role"</span>,
            <span class="hljs-string">"name"</span>:  <span class="hljs-string">"user.name"</span>,
        },
        <span class="hljs-comment">// CustomClaims claims validation function</span>
        ValidateClaims: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c okapi.Context, claims jwt.Claims)</span> <span class="hljs-title">error</span></span> {
            slog.Info(<span class="hljs-string">"Validating JWT claims for role using custom function"</span>)
            <span class="hljs-comment">// Simulate a custom claims validation</span>
            mapClaims, ok := claims.(jwt.MapClaims)
            <span class="hljs-keyword">if</span> !ok {
                <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"invalid claims type"</span>)
            }
            <span class="hljs-keyword">if</span> role, exist := mapClaims[<span class="hljs-string">"user"</span>].(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{})[<span class="hljs-string">"role"</span>]; exist {
                fmt.Println(<span class="hljs-string">"Role from claims:"</span>, role)
            }
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
        },
    }
    jwtClaims = jwt.MapClaims{
        <span class="hljs-string">"sub"</span>: <span class="hljs-string">"12345"</span>,
        <span class="hljs-string">"iss"</span>: <span class="hljs-string">"okapi.example.com"</span>,
        <span class="hljs-string">"aud"</span>: <span class="hljs-string">"okapi.example.com"</span>,
        <span class="hljs-string">"user"</span>: <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>{
            <span class="hljs-string">"name"</span>:  <span class="hljs-string">""</span>,
            <span class="hljs-string">"role"</span>:  <span class="hljs-string">""</span>,
            <span class="hljs-string">"email"</span>: <span class="hljs-string">""</span>,
        },
        <span class="hljs-string">"email_verified"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-string">"permissions"</span>:    []<span class="hljs-keyword">string</span>{<span class="hljs-string">"read"</span>, <span class="hljs-string">"create"</span>},
        <span class="hljs-string">"exp"</span>:            time.Now().Add(<span class="hljs-number">2</span> * time.Hour).Unix(),
    }
    adminPermissions = []<span class="hljs-keyword">string</span>{<span class="hljs-string">"read"</span>, <span class="hljs-string">"create"</span>, <span class="hljs-string">"delete"</span>, <span class="hljs-string">"update"</span>}
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Login</span><span class="hljs-params">(authRequest *models.AuthRequest)</span> <span class="hljs-params">(models.AuthResponse, error)</span></span> {
    <span class="hljs-comment">// This is where you would typically validate the user credentials against a database</span>

    slog.Info(<span class="hljs-string">"Login attempt"</span>, <span class="hljs-string">"username"</span>, authRequest.Username)
    <span class="hljs-comment">// Simulate a login function that returns a JWT token</span>
    <span class="hljs-keyword">if</span> authRequest.Username != <span class="hljs-string">"admin"</span> &amp;&amp; authRequest.Password != <span class="hljs-string">"password"</span> ||
        authRequest.Username != <span class="hljs-string">"user"</span> &amp;&amp; authRequest.Password != <span class="hljs-string">"password"</span> {
        <span class="hljs-keyword">return</span> models.AuthResponse{
            Success: <span class="hljs-literal">false</span>,
            Message: <span class="hljs-string">"Invalid username or password"</span>,
        }, fmt.Errorf(<span class="hljs-string">"username or password is wrong"</span>)
    }

    <span class="hljs-keyword">if</span> _, ok := jwtClaims[<span class="hljs-string">"user"</span>].(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>); ok {
        jwtClaims[<span class="hljs-string">"user"</span>].(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>)[<span class="hljs-string">"name"</span>] = strings.ToUpper(authRequest.Username)
        jwtClaims[<span class="hljs-string">"user"</span>].(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>)[<span class="hljs-string">"role"</span>] = authRequest.Username
        jwtClaims[<span class="hljs-string">"user"</span>].(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>)[<span class="hljs-string">"email"</span>] = authRequest.Username + <span class="hljs-string">"@example.com"</span>
        jwtClaims[<span class="hljs-string">"permissions"</span>] = []<span class="hljs-keyword">string</span>{<span class="hljs-string">"read"</span>}

        <span class="hljs-comment">// If the user is an admin, add admin permissions</span>
        <span class="hljs-keyword">if</span> authRequest.Username == <span class="hljs-string">"admin"</span> {
            jwtClaims[<span class="hljs-string">"permissions"</span>] = adminPermissions
        }

    }
    <span class="hljs-comment">// Set the expiration time for the JWT token</span>
    expireAt := <span class="hljs-number">30</span> * time.Minute
    jwtClaims[<span class="hljs-string">"exp"</span>] = time.Now().Add(expireAt).Unix()

    token, err := okapi.GenerateJwtToken(JWTAuth.SigningSecret, jwtClaims, expireAt)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {

        <span class="hljs-keyword">return</span> models.AuthResponse{
            Success: <span class="hljs-literal">false</span>,
            Message: <span class="hljs-string">"Invalid username or password"</span>,
        }, fmt.Errorf(<span class="hljs-string">"failed to generate JWT token: %w"</span>, err)
    }
    <span class="hljs-keyword">return</span> models.AuthResponse{
        Success:   <span class="hljs-literal">true</span>,
        Message:   <span class="hljs-string">"Welcome back "</span> + authRequest.Username,
        Token:     token,
        ExpiresAt: time.Now().Add(expireAt).Unix(),
    }, <span class="hljs-literal">nil</span>

}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">CustomMiddleware</span><span class="hljs-params">(next okapi.HandleFunc)</span> <span class="hljs-title">okapi</span>.<span class="hljs-title">HandleFunc</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
        slog.Info(<span class="hljs-string">"Custom middleware executed"</span>, <span class="hljs-string">"path"</span>, c.Request().URL.Path, <span class="hljs-string">"method"</span>, c.Request().Method)
        <span class="hljs-comment">// You can add any custom logic here, such as logging, authentication, etc.</span>
        <span class="hljs-comment">// For example, let's log the request method and URL</span>
        slog.Info(<span class="hljs-string">"Request received"</span>, <span class="hljs-string">"method"</span>, c.Request().Method, <span class="hljs-string">"url"</span>, c.Request().URL.String())
        <span class="hljs-comment">// Call the next handler in the chain</span>
        <span class="hljs-keyword">if</span> err := next(c); err != <span class="hljs-literal">nil</span> {
            <span class="hljs-comment">// If an error occurs, log it and return a generic error response</span>
            slog.Error(<span class="hljs-string">"Error in custom middleware"</span>, <span class="hljs-string">"error"</span>, err)
            <span class="hljs-keyword">return</span> c.JSON(http.StatusInternalServerError, okapi.M{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Internal Server Error"</span>})
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }
}
</code></pre>
<hr />
<h3 id="heading-3-create-controllers">3. Create Controllers</h3>
<p><code>controllers/controller.go</code>:</p>
<blockquote>
<p>Implements handlers for Home, Book, and Authentication logic.</p>
</blockquote>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> controllers

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"github.com/jkaninda/okapi"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"okapi-example/middlewares"</span>
    <span class="hljs-string">"okapi-example/models"</span>
    <span class="hljs-string">"strconv"</span>
)

<span class="hljs-keyword">type</span> BookController <span class="hljs-keyword">struct</span>{}
<span class="hljs-keyword">type</span> CommonController <span class="hljs-keyword">struct</span>{}
<span class="hljs-keyword">type</span> AuthController <span class="hljs-keyword">struct</span>{}

<span class="hljs-keyword">var</span> (
    books = []*models.Book{
        {Id: <span class="hljs-number">1</span>, Name: <span class="hljs-string">"The Go Programming Language "</span>, Price: <span class="hljs-number">100</span>},
        {Id: <span class="hljs-number">2</span>, Name: <span class="hljs-string">"Building REST/API With Okapi "</span>, Price: <span class="hljs-number">50</span>},
        {Id: <span class="hljs-number">3</span>, Name: <span class="hljs-string">"Learning Go"</span>, Price: <span class="hljs-number">200</span>},
        {Id: <span class="hljs-number">4</span>, Name: <span class="hljs-string">"Go Web Programming"</span>, Price: <span class="hljs-number">300</span>},
        {Id: <span class="hljs-number">5</span>, Name: <span class="hljs-string">"Go in Action"</span>, Price: <span class="hljs-number">150</span>},
    }
    ApiVersion = <span class="hljs-string">"V1"</span>
)

<span class="hljs-comment">// ****************** Controllers *****************</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(hc *CommonController)</span> <span class="hljs-title">Home</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">return</span> c.OK(okapi.M{<span class="hljs-string">"message"</span>: <span class="hljs-string">"Welcome to the Okapi Web Framework!"</span>})
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(hc *CommonController)</span> <span class="hljs-title">Version</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">return</span> c.OK(okapi.M{<span class="hljs-string">"version"</span>: ApiVersion})
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(bc *BookController)</span> <span class="hljs-title">GetBooks</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// Simulate fetching books from a database</span>
    <span class="hljs-keyword">return</span> c.OK(books)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(bc *BookController)</span> <span class="hljs-title">CreateBook</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// Simulate creating a book in a database</span>
    book := &amp;models.Book{}
    err := c.Bind(book)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> c.ErrorBadRequest(models.ErrorResponse{Success: <span class="hljs-literal">false</span>, Status: http.StatusBadRequest, Details: err.Error()})
    }
    book.Id = <span class="hljs-built_in">len</span>(books) + <span class="hljs-number">1</span>
    books = <span class="hljs-built_in">append</span>(books, book)
    response := models.Response{
        Success: <span class="hljs-literal">true</span>,
        Message: <span class="hljs-string">"Book created successfully"</span>,
        Data:    *book,
    }
    <span class="hljs-keyword">return</span> c.OK(response)
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(bc *BookController)</span> <span class="hljs-title">GetBook</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
    id := c.Param(<span class="hljs-string">"id"</span>)
    i, err := strconv.Atoi(id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> c.ErrorBadRequest(models.ErrorResponse{Success: <span class="hljs-literal">false</span>, Status: http.StatusBadRequest, Details: err.Error()})
    }
    <span class="hljs-comment">// Simulate a fetching book from a database</span>

    <span class="hljs-keyword">for</span> _, book := <span class="hljs-keyword">range</span> books {
        <span class="hljs-keyword">if</span> book.Id == i {
            <span class="hljs-keyword">return</span> c.OK(book)
        }
    }
    <span class="hljs-keyword">return</span> c.AbortNotFound(<span class="hljs-string">"Book not found"</span>)
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(bc *BookController)</span> <span class="hljs-title">DeleteBook</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
    id := c.Param(<span class="hljs-string">"id"</span>)
    i, err := strconv.Atoi(id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> c.ErrorBadRequest(models.ErrorResponse{Success: <span class="hljs-literal">false</span>, Status: http.StatusBadRequest, Details: err.Error()})
    }

    <span class="hljs-comment">// Simulate deleting a book from a database</span>
    <span class="hljs-keyword">for</span> index, book := <span class="hljs-keyword">range</span> books {
        <span class="hljs-keyword">if</span> book.Id == i {
            books = <span class="hljs-built_in">append</span>(books[:index], books[index+<span class="hljs-number">1</span>:]...)
            <span class="hljs-keyword">return</span> c.OK(models.Response{
                Success: <span class="hljs-literal">true</span>,
                Message: <span class="hljs-string">"Book deleted successfully"</span>,
            })
        }
    }
    <span class="hljs-keyword">return</span> c.AbortNotFound(<span class="hljs-string">"Book not found"</span>)
}

<span class="hljs-comment">// ******************** AuthController *****************</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(bc *AuthController)</span> <span class="hljs-title">Login</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
    authRequest := &amp;models.AuthRequest{}
    err := c.Bind(authRequest)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> c.ErrorBadRequest(models.ErrorResponse{Success: <span class="hljs-literal">false</span>, Status: http.StatusBadRequest, Details: err.Error()})
    }
    <span class="hljs-comment">// Validate the authRequest and generate a JWT token</span>
    authResponse, err := middlewares.Login(authRequest)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> c.ErrorUnauthorized(authResponse)
    }
    <span class="hljs-keyword">return</span> c.OK(authResponse)
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(bc *AuthController)</span> <span class="hljs-title">WhoAmI</span><span class="hljs-params">(c okapi.Context)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">//Get User Information from the context, shared by the JWT middleware using forwardClaims</span>
    email := c.GetString(<span class="hljs-string">"email"</span>)
    <span class="hljs-keyword">if</span> email == <span class="hljs-string">""</span> {
        <span class="hljs-keyword">return</span> c.AbortUnauthorized(<span class="hljs-string">"Unauthorized"</span>, fmt.Errorf(<span class="hljs-string">"user not authenticated"</span>))
    }

    <span class="hljs-comment">// Respond with the current user information</span>
    <span class="hljs-keyword">return</span> c.OK(models.UserInfo{
        Email: email,
        Role:  c.GetString(<span class="hljs-string">"role"</span>),
        Name:  c.GetString(<span class="hljs-string">"name"</span>),
    },
    )
}
</code></pre>
<hr />
<h3 id="heading-4-define-routes">4. Define Routes</h3>
<p><code>routes/route.go</code>:</p>
<blockquote>
<p>Group routes by feature and use route definitions with OpenAPI documentation metadata.</p>
</blockquote>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> routes

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"okapi-example/controllers"</span>
    <span class="hljs-string">"okapi-example/middlewares"</span>
    <span class="hljs-string">"okapi-example/models"</span>

    <span class="hljs-string">"github.com/jkaninda/okapi"</span>
)

<span class="hljs-comment">// ****************** Controllers ******************</span>
<span class="hljs-keyword">var</span> (
    bookController   = &amp;controllers.BookController{}
    commonController = &amp;controllers.CommonController{}
    authController   = &amp;controllers.AuthController{}
)

<span class="hljs-keyword">type</span> Route <span class="hljs-keyword">struct</span> {
    <span class="hljs-comment">// app is the Okapi application</span>
    app *okapi.Okapi
}

<span class="hljs-comment">// NewRoute creates a new Route instance with the Okapi application</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewRoute</span><span class="hljs-params">(app *okapi.Okapi)</span> *<span class="hljs-title">Route</span></span> {
    <span class="hljs-comment">// Update OpenAPI documentation with the application title and version</span>
    app.WithOpenAPIDocs(okapi.OpenAPI{
        Title:   <span class="hljs-string">"REST APIs with Okapi Framework"</span>,
        Version: controllers.ApiVersion,
        Licence: okapi.License{
            Name: <span class="hljs-string">"MIT"</span>,
            URL:  <span class="hljs-string">"https://opensource.org/license/mit/"</span>,
        },
    })
    <span class="hljs-keyword">return</span> &amp;Route{
        app: app,
    }
}

<span class="hljs-comment">// ****************** Routes Definition ******************</span>

<span class="hljs-comment">// Home returns the route definition for the Home endpoint</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Route)</span> <span class="hljs-title">Home</span><span class="hljs-params">()</span> <span class="hljs-title">okapi</span>.<span class="hljs-title">RouteDefinition</span></span> {
    <span class="hljs-keyword">return</span> okapi.RouteDefinition{
        Path:    <span class="hljs-string">"/"</span>,
        Method:  http.MethodGet,
        Handler: commonController.Home,
        Group:   &amp;okapi.Group{Prefix: <span class="hljs-string">"/"</span>, Tags: []<span class="hljs-keyword">string</span>{<span class="hljs-string">"CommonController"</span>}},
        Options: []okapi.RouteOption{
            okapi.DocSummary(<span class="hljs-string">"Home"</span>),
            okapi.DocDescription(<span class="hljs-string">"Welcome to the Okapi Web Framework!"</span>),
        },
    }
}

<span class="hljs-comment">// Version returns the route definition for the Version endpoint</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Route)</span> <span class="hljs-title">Version</span><span class="hljs-params">()</span> <span class="hljs-title">okapi</span>.<span class="hljs-title">RouteDefinition</span></span> {
    <span class="hljs-keyword">return</span> okapi.RouteDefinition{
        Path:    <span class="hljs-string">"/version"</span>,
        Method:  http.MethodGet,
        Handler: commonController.Version,
        Group:   &amp;okapi.Group{Prefix: <span class="hljs-string">"/api/v1"</span>, Tags: []<span class="hljs-keyword">string</span>{<span class="hljs-string">"CommonController"</span>}},
        Options: []okapi.RouteOption{
            okapi.DocSummary(<span class="hljs-string">"API Version"</span>),
            okapi.DocDescription(<span class="hljs-string">"Get the API version"</span>),
            okapi.DocResponse(okapi.M{<span class="hljs-string">"version"</span>: <span class="hljs-string">"v1"</span>}),
        },
    }
}

<span class="hljs-comment">// ************* Book Routes *************</span>
<span class="hljs-comment">// In this section, we will make BookRoutes deprecated and create BookV1Routes</span>

<span class="hljs-comment">// BookRoutes returns the route definitions for the BookController</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Route)</span> <span class="hljs-title">BookRoutes</span><span class="hljs-params">()</span> []<span class="hljs-title">okapi</span>.<span class="hljs-title">RouteDefinition</span></span> {
    apiGroup := &amp;okapi.Group{Prefix: <span class="hljs-string">"/api"</span>, Tags: []<span class="hljs-keyword">string</span>{<span class="hljs-string">"BookController"</span>}}
    <span class="hljs-comment">// Mark the group as deprecated</span>
    <span class="hljs-comment">// But, it will still be available for use, it's just marked as deprecated on the OpenAPI documentation</span>
    apiGroup.Deprecated()
    <span class="hljs-comment">// Apply custom middleware</span>
    apiGroup.Use(middlewares.CustomMiddleware)
    <span class="hljs-keyword">return</span> []okapi.RouteDefinition{
        {
            Method:  http.MethodGet,
            Path:    <span class="hljs-string">"/books"</span>,
            Handler: bookController.GetBooks,
            Group:   apiGroup,
            Options: []okapi.RouteOption{
                okapi.DocSummary(<span class="hljs-string">"Get Books"</span>),
                okapi.DocDescription(<span class="hljs-string">"Retrieve a list of books"</span>),
                okapi.DocResponse([]models.Book{}),
                okapi.DocResponse(http.StatusBadRequest, models.ErrorResponse{}),
                okapi.DocResponse(http.StatusNotFound, models.ErrorResponse{}),
            },
        },
        {
            Method:  http.MethodGet,
            Path:    <span class="hljs-string">"/books/:id"</span>,
            Handler: bookController.GetBook,
            Group:   apiGroup,
            Options: []okapi.RouteOption{
                okapi.DocSummary(<span class="hljs-string">"Get Book by ID"</span>),
                okapi.DocDescription(<span class="hljs-string">"Retrieve a book by its ID"</span>),
                okapi.DocPathParam(<span class="hljs-string">"id"</span>, <span class="hljs-string">"int"</span>, <span class="hljs-string">"The ID of the book"</span>),
                okapi.DocResponse(models.Book{}),
                okapi.DocResponse(http.StatusBadRequest, models.ErrorResponse{}),
                okapi.DocResponse(http.StatusNotFound, models.ErrorResponse{}),
            },
        },
    }
}

<span class="hljs-comment">// *************** End of Book Routes ***************</span>

<span class="hljs-comment">// *********************** Book v1 Routes ***********************</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Route)</span> <span class="hljs-title">V1BookRoutes</span><span class="hljs-params">()</span> []<span class="hljs-title">okapi</span>.<span class="hljs-title">RouteDefinition</span></span> {
    apiGroup := &amp;okapi.Group{Prefix: <span class="hljs-string">"/api"</span>}
    apiV1Group := apiGroup.Group(<span class="hljs-string">"/v1"</span>).WithTags([]<span class="hljs-keyword">string</span>{<span class="hljs-string">"BookController"</span>})
    <span class="hljs-comment">// Apply custom middleware</span>
    apiGroup.Use(middlewares.CustomMiddleware)
    <span class="hljs-keyword">return</span> []okapi.RouteDefinition{
        {
            Method:  http.MethodGet,
            Path:    <span class="hljs-string">"/books"</span>,
            Handler: bookController.GetBooks,
            Group:   apiV1Group,
            Options: []okapi.RouteOption{
                okapi.DocSummary(<span class="hljs-string">"Get Books"</span>),
                okapi.DocDescription(<span class="hljs-string">"Retrieve a list of books"</span>),
                okapi.DocResponse([]models.Book{}),
                okapi.DocResponse(http.StatusBadRequest, models.ErrorResponse{}),
            },
        },
        {
            Method:  http.MethodGet,
            Path:    <span class="hljs-string">"/books/:id"</span>,
            Handler: bookController.GetBook,
            Group:   apiV1Group,
            Options: []okapi.RouteOption{
                okapi.DocSummary(<span class="hljs-string">"Get Book by ID"</span>),
                okapi.DocDescription(<span class="hljs-string">"Retrieve a book by its ID"</span>),
                okapi.DocPathParam(<span class="hljs-string">"id"</span>, <span class="hljs-string">"int"</span>, <span class="hljs-string">"The ID of the book"</span>),
                okapi.DocResponse(models.Book{}),
                okapi.DocResponse(http.StatusBadRequest, models.ErrorResponse{}),
                okapi.DocResponse(http.StatusNotFound, models.ErrorResponse{}),
            },
        },
    }
}

<span class="hljs-comment">// *************** Auth Routes ****************</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Route)</span> <span class="hljs-title">AuthRoute</span><span class="hljs-params">()</span> <span class="hljs-title">okapi</span>.<span class="hljs-title">RouteDefinition</span></span> {
    apiGroup := &amp;okapi.Group{Prefix: <span class="hljs-string">"/api/v1/auth"</span>, Tags: []<span class="hljs-keyword">string</span>{<span class="hljs-string">"AuthController"</span>}}
    apiGroup.Use(middlewares.CustomMiddleware)
    <span class="hljs-keyword">return</span> okapi.RouteDefinition{

        Method:  http.MethodPost,
        Path:    <span class="hljs-string">"/login"</span>,
        Handler: authController.Login,
        Group:   apiGroup,
        Options: []okapi.RouteOption{
            okapi.DocSummary(<span class="hljs-string">"Login"</span>),
            okapi.DocDescription(<span class="hljs-string">"User login to get a JWT token"</span>),
            okapi.DocRequestBody(models.AuthRequest{}),
            okapi.DocResponse(models.AuthResponse{}),
            okapi.DocResponse(http.StatusUnauthorized, models.AuthResponse{}),
        },
    }
}

<span class="hljs-comment">// ************** Authenticated Routes **************</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Route)</span> <span class="hljs-title">SecurityRoutes</span><span class="hljs-params">()</span> []<span class="hljs-title">okapi</span>.<span class="hljs-title">RouteDefinition</span></span> {
    coreGroup := &amp;okapi.Group{Prefix: <span class="hljs-string">"/api/v1/security"</span>, Tags: []<span class="hljs-keyword">string</span>{<span class="hljs-string">"SecurityController"</span>}}
    <span class="hljs-comment">// Apply JWT authentication middleware to the admin group</span>
    coreGroup.Use(middlewares.JWTAuth.Middleware)
    <span class="hljs-comment">// Apply custom middleware</span>
    coreGroup.Use(middlewares.CustomMiddleware)
    coreGroup.WithBearerAuth() <span class="hljs-comment">//Enable Bearer token for OpenAPI documentation</span>
    <span class="hljs-keyword">return</span> []okapi.RouteDefinition{
        {
            Method:  http.MethodPost,
            Path:    <span class="hljs-string">"/whoami"</span>,
            Handler: authController.WhoAmI,
            Group:   coreGroup,
            Options: []okapi.RouteOption{
                okapi.DocSummary(<span class="hljs-string">"Whoami"</span>),
                okapi.DocDescription(<span class="hljs-string">"Get the current user's information"</span>),
                okapi.DocResponse(models.UserInfo{}),
            },
        },
    }
}

<span class="hljs-comment">// ***************** Admin Routes *****************</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Route)</span> <span class="hljs-title">AdminRoutes</span><span class="hljs-params">()</span> []<span class="hljs-title">okapi</span>.<span class="hljs-title">RouteDefinition</span></span> {
    apiGroup := &amp;okapi.Group{Prefix: <span class="hljs-string">"/api/v1/admin"</span>, Tags: []<span class="hljs-keyword">string</span>{<span class="hljs-string">"AdminController"</span>}}
    <span class="hljs-comment">// Apply JWT authentication middleware to the admin group</span>
    apiGroup.Use(middlewares.JWTAuth.Middleware)
    apiGroup.Use(middlewares.CustomMiddleware)
    apiGroup.WithBearerAuth() <span class="hljs-comment">//Enable Bearer token for OpenAPI documentation</span>

    <span class="hljs-keyword">return</span> []okapi.RouteDefinition{

        {
            Method:  http.MethodPost,
            Path:    <span class="hljs-string">"/books"</span>,
            Handler: bookController.CreateBook,
            Group:   apiGroup,
            Options: []okapi.RouteOption{
                okapi.DocSummary(<span class="hljs-string">"Create Book"</span>),
                okapi.DocDescription(<span class="hljs-string">"Create a new book"</span>),
                okapi.DocRequestBody(models.Book{}),
                okapi.DocResponse(models.Response{}),
            },
        },
        {
            Method:  http.MethodDelete,
            Path:    <span class="hljs-string">"/books/:id"</span>,
            Handler: bookController.DeleteBook,
            Group:   apiGroup,
            Options: []okapi.RouteOption{
                okapi.DocSummary(<span class="hljs-string">"Delete Book by ID"</span>),
                okapi.DocDescription(<span class="hljs-string">"Delete a book by its ID"</span>),
                okapi.DocPathParam(<span class="hljs-string">"id"</span>, <span class="hljs-string">"int"</span>, <span class="hljs-string">"The ID of the book"</span>),
                okapi.DocResponse(models.Response{}),
                okapi.DocResponse(http.StatusNotFound, models.ErrorResponse{}),
                okapi.DocResponse(http.StatusUnauthorized, models.ErrorResponse{}),
            },
        },
    }
}
</code></pre>
<hr />
<h3 id="heading-5-main-entry-point">5. Main Entry Point</h3>
<p><code>main.go</code>:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"github.com/jkaninda/okapi"</span>
    <span class="hljs-string">"okapi-example/routes"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    app := okapi.Default()

    <span class="hljs-comment">// Create the route instance</span>
    route := routes.NewRoute(app)
    <span class="hljs-comment">// Register routes</span>
    app.Register(route.Home())
    app.Register(route.Version())
    app.Register(route.AdminRoutes()...)
    app.Register(route.AuthRoute())
    app.Register(route.SecurityRoutes()...)
    app.Register(route.BookRoutes()...)
    app.Register(route.V1BookRoutes()...)

    <span class="hljs-comment">// Start the server</span>
    <span class="hljs-keyword">if</span> err := app.Start(); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

}
</code></pre>
<hr />
<h2 id="heading-run-the-server">Run the Server</h2>
<pre><code class="lang-bash">go run main.go
</code></pre>
<h3 id="heading-access-the-api">Access the API</h3>
<ul>
<li><p>Visit: <a target="_blank" href="http://localhost:8080/">http://localhost:8080</a></p>
</li>
<li><p>Swagger UI: <a target="_blank" href="http://localhost:8080/docs">http://localhost:8080/docs</a></p>
</li>
</ul>
<p>Example response:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Welcome to the Okapi Web Framework!"</span>
}
</code></pre>
<hr />
<h3 id="heading-swagger-ui">Swagger UI</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750667316513/de2ed73a-a4c8-46a6-8457-ff4527e057d5.png" alt="Okapi OpenAPI Swagger UI" class="image--center mx-auto" /></p>
<h2 id="heading-summary">Summary</h2>
<p>With Okapi, you can create well-structured, RESTful APIs in Go with minimal effort. Features like declarative routing, built-in JWT authentication, and automatic OpenAPI documentation make it ideal for modern backend development.</p>
<h4 id="heading-okapi-github-httpsgithubcomjkanindaokapihttpsgithubcomjkanindaokapi">Okapi Github: <a target="_blank" href="https://github.com/jkaninda/okapi">https://github.com/jkaninda/okapi</a></h4>
<h4 id="heading-source-code-httpsgithubcomjkanindaokapitreemainexamplesroute-definitionhttpsgithubcomjkanindaokapitreemainexamplesroute-definition">Source code: <a target="_blank" href="https://github.com/jkaninda/okapi/tree/main/examples/route-definition">https://github.com/jkaninda/okapi/tree/main/examples/route-definition</a></h4>
<h3 id="heading-next-steps">Next Steps</h3>
<ul>
<li><p>Add database integration (e.g., PostgreSQL with GORM)</p>
</li>
<li><p>Integrate unit tests</p>
</li>
<li><p>Deploy to Docker or cloud platforms</p>
</li>
<li><p>Secure your JWT secrets using environment variables</p>
</li>
</ul>
<hr />
]]></content:encoded></item><item><title><![CDATA[S3Safe: A Lightweight CLI Tool for S3 Backups and Restores]]></title><description><![CDATA[Managing backups and restores for Amazon S3 (or S3-compatible storage) shouldn’t be complicated. That’s where S3Safe comes in—a lightweight, and flexible CLI tool designed to simplify your backup and restore workflows.
🔐 What is S3Safe?
S3Safe is an...]]></description><link>https://blog.jkaninda.dev/s3safe-a-lightweight-cli-tool-for-s3-backups-and-restores</link><guid isPermaLink="true">https://blog.jkaninda.dev/s3safe-a-lightweight-cli-tool-for-s3-backups-and-restores</guid><category><![CDATA[s3safe]]></category><category><![CDATA[s3backup]]></category><category><![CDATA[Backup]]></category><dc:creator><![CDATA[Jonas Kaninda]]></dc:creator><pubDate>Wed, 21 May 2025 10:55:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747884314829/4d4e3f93-f377-4ed5-b9b8-2b7115b92ad9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Managing backups and restores for Amazon S3 (or S3-compatible storage) shouldn’t be complicated. That’s where <strong>S3Safe</strong> comes in—a lightweight, and flexible CLI tool designed to simplify your backup and restore workflows.</p>
<h3 id="heading-what-is-s3safehttpsgithubcomjkanindas3safe">🔐 What is S3Saf<a target="_blank" href="https://github.com/jkaninda/s3safe">e?</a></h3>
<p><a target="_blank" href="https://github.com/jkaninda/s3safe">S3Safe</a> is an open<a target="_blank" href="https://github.com/jkaninda/s3safe">-sourc</a>e command-line tool designed for efficient, and flexible backup and restore operations involving Amazon S3 and any S3-compatible storage (e.g., Wasabi, MinIO, etc.). Whether you're backing up application data, logs, or system snapshots, S3Safe offers a clean and intuitive interface to handle these tasks reliably.</p>
<h2 id="heading-key-features">🔑 Key Features</h2>
<p>✅ <strong>Compression Support</strong> – Backup with <strong>gzip/tar</strong> compression to save space.<br />✅ <strong>Flexible Operations</strong> – Backup entire directories, single files, or use recursive operations.<br />✅ <strong>Exclusion Patterns</strong> – Skip unwanted files during backup.<br />✅ <strong>Docker Support</strong> – Run S3Safe in containerized environments.</p>
<h2 id="heading-installation">🚀 Installation</h2>
<h3 id="heading-via-go">Via Go</h3>
<pre><code class="lang-sh">go install github.com/jkaninda/s3safe@latest
</code></pre>
<h3 id="heading-via-docker">Via Docker</h3>
<pre><code class="lang-sh">docker pull jkaninda/s3safe:latest
</code></pre>
<h2 id="heading-configuration">⚙️ Configuration</h2>
<p>Copy <code>.env.example</code> to <code>.env</code> and configure your S3 credentials:</p>
<pre><code class="lang-ini"><span class="hljs-attr">AWS_REGION</span>=us-east-<span class="hljs-number">1</span>
<span class="hljs-attr">AWS_ENDPOINT</span>=https://s3.wasabisys.com  <span class="hljs-comment"># For S3-compatible storage</span>
<span class="hljs-attr">AWS_ACCESS_KEY_ID</span>=your_access_key
<span class="hljs-attr">AWS_SECRET_KEY</span>=your_secret_key
<span class="hljs-attr">AWS_BUCKET</span>=your_bucket_name
<span class="hljs-attr">AWS_FORCE_PATH</span>=<span class="hljs-string">"true"</span>  <span class="hljs-comment"># Required for path-style URLs</span>
<span class="hljs-attr">AWS_DISABLE_SSL</span>=<span class="hljs-string">"false"</span>  <span class="hljs-comment"># Set "true" for non-HTTPS endpoints</span>
</code></pre>
<h3 id="heading-command-overview">🛠️ Command Overview</h3>
<h4 id="heading-global-options">Global Options</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Option</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>--path</code>, <code>-p</code></td><td>Source path (file or directory)</td></tr>
<tr>
<td><code>--dest</code>, <code>-d</code></td><td>Destination path (S3 or local)</td></tr>
<tr>
<td><code>--recursive</code>, <code>-r</code></td><td>Recursively process directories</td></tr>
<tr>
<td><code>--exclude</code>, <code>-e</code></td><td>Comma-separated exclude patterns</td></tr>
<tr>
<td><code>--file</code>, <code>-f</code></td><td>Use a single file instead of a directory</td></tr>
<tr>
<td><code>--ignore-errors</code>, <code>-i</code></td><td>Continue on restore errors</td></tr>
<tr>
<td><code>--env-file</code></td><td>Custom <code>.env</code> file (default: <code>.env</code>)</td></tr>
<tr>
<td><code>--bucket</code>, <code>-b</code></td><td>S3 bucket name</td></tr>
</tbody>
</table>
</div><h4 id="heading-backup-options">Backup Options</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Option</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>--compress</code>, <code>-c</code></td><td>Compress the backup as <code>.tar.gz</code></td></tr>
<tr>
<td><code>--timestamp</code>, <code>-t</code></td><td>Add a timestamp to the filename</td></tr>
</tbody>
</table>
</div><h4 id="heading-restore-options">Restore Options</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Option</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>--decompress</code>, <code>-D</code></td><td>Decompress after download</td></tr>
<tr>
<td><code>--force</code></td><td>Force overwrite during the restore</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-usage-examples">💡 Usage Examples</h2>
<h3 id="heading-backup-operations"><strong>Backup Operations</strong></h3>
<p><strong>Backup a directory (compressed with timestamp):</strong></p>
<pre><code class="lang-sh">s3safe backup -p ./backups -d /s3path --compress --timestamp
</code></pre>
<p><strong>Backup a single file:</strong></p>
<pre><code class="lang-sh">s3safe backup --file data.db --dest /s3path/db-backups --compress
</code></pre>
<p><strong>Non-compressed recursive backup:</strong></p>
<pre><code class="lang-sh">s3safe backup -p ./backups -d /s3path/backups -r
</code></pre>
<h3 id="heading-restore-operations"><strong>Restore Operations</strong></h3>
<p><strong>Restore &amp; decompress a backup:</strong></p>
<pre><code class="lang-sh">s3safe restore -p /s3path/backup.tar.gz -d ./restored --decompress
</code></pre>
<p><strong>Restore a directory (recursive):</strong></p>
<pre><code class="lang-sh">s3safe restore --path /s3path --dest ./restored --recursive
</code></pre>
<h3 id="heading-running-with-docker">🐳 Running with Docker</h3>
<p>There is no need to install Go or manage dependencies locally—just run it in Docker:</p>
<p><strong>Backup with Docker:</strong></p>
<pre><code class="lang-sh">docker run --rm --env-file .env \
  -v <span class="hljs-string">"./backups:/backups"</span> \
  jkaninda/s3safe:latest \
  backup --path /backups -d s3path --compress
</code></pre>
<p><strong>Restore with Docker:</strong></p>
<pre><code class="lang-sh">docker run --rm --env-file .env \
  -v <span class="hljs-string">"./restored:/restored"</span> \
  jkaninda/s3safe:latest \
  restore --path s3path/backup.tar.gz -d /restored --decompress
</code></pre>
<hr />
<p>🔗 <strong>GitHub</strong>: <a target="_blank" href="https://github.com/jkaninda/s3safe">https://github.com/jkaninda/s3safe</a></p>
<p>🚀 <strong>Happy Backing Up!</strong> 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Automating Nightly Backups for Postgres Databases in Kubernetes with PG-BKUP]]></title><description><![CDATA[Backing up your data is a critical practice, especially when managing databases in Kubernetes environments. Regular backups of your Postgres database can prevent data loss, ensure business continuity, and provide peace of mind. This guide provides a ...]]></description><link>https://blog.jkaninda.dev/automating-nightly-backups-for-postgres-databases-in-kubernetes-with-pg-bkup</link><guid isPermaLink="true">https://blog.jkaninda.dev/automating-nightly-backups-for-postgres-databases-in-kubernetes-with-pg-bkup</guid><category><![CDATA[postgres-backup]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Jonas Kaninda]]></dc:creator><pubDate>Mon, 27 Jan 2025 08:15:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747825608724/de44129a-8ae6-40ac-8da1-ed44f35bd809.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Backing up your data is a critical practice, especially when managing databases in Kubernetes environments. Regular backups of your Postgres database can prevent data loss, ensure business continuity, and provide peace of mind. This guide provides a comprehensive walkthrough on automating nightly backups for a Postgres database running inside a Kubernetes container using the <strong>PG-BKUP</strong> tool.</p>
<p><strong>PG-BKUP</strong> is a powerful container image designed to simplify the backup, restore, and migration of PostgreSQL databases. It supports multiple storage options, including local storage, S3, SFTP, and Azure Blob, and ensures data security through GPG encryption. Whether you're managing a single database or multiple databases, PG-BKUP offers the flexibility and reliability you need.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving into the backup process, ensure the following are in place:</p>
<ul>
<li>A running Kubernetes cluster.</li>
<li>A PostgreSQL database deployed in Kubernetes.</li>
<li>(Optional) S3-compatible, SFTP, or Azure Blob storage for remote backups.</li>
</ul>
<h2 id="heading-backup-strategies-with-pg-bkup">Backup Strategies with PG-BKUP</h2>
<p>PG-BKUP can be deployed on Kubernetes as a <strong>Job</strong>, <strong>CronJob</strong>, or <strong>Deployment</strong> using its integrated scheduled mode. Below, we’ll explore two common approaches: using a <strong>CronJob</strong> for scheduled backups and a <strong>Deployment</strong> for backing up multiple databases.</p>
<h3 id="heading-1-backup-using-a-kubernetes-cronjob">1. Backup Using a Kubernetes CronJob</h3>
<p>A <strong>CronJob</strong> is ideal for scheduling regular backups, such as nightly backups. Here’s how to set it up:</p>
<h4 id="heading-step-1-create-a-kubernetes-secret">Step 1: Create a Kubernetes Secret</h4>
<p>Store sensitive information like database credentials and AWS keys in a Kubernetes Secret. Use the following commands to encode your credentials:</p>
<pre><code class="lang-sh"><span class="hljs-built_in">echo</span> -n <span class="hljs-string">"username"</span> | base64
<span class="hljs-built_in">echo</span> -n <span class="hljs-string">"password"</span> | base64
</code></pre>
<p>Then, create a Kubernetes Secret YAML file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Secret</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">backup-secret</span>
<span class="hljs-attr">type:</span> <span class="hljs-string">Opaque</span>
<span class="hljs-attr">data:</span>
  <span class="hljs-attr">username:</span> <span class="hljs-string">dXNlcm5hbWU=</span>  <span class="hljs-comment"># Replace with your base64-encoded username</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">cGFzc3dvcmQ=</span>  <span class="hljs-comment"># Replace with your base64-encoded password</span>
  <span class="hljs-attr">aws_access_key_id:</span> <span class="hljs-string">YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4</span>  <span class="hljs-comment"># Replace with your base64-encoded AWS access key</span>
  <span class="hljs-attr">aws_secret_access_key:</span> <span class="hljs-string">YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4</span>  <span class="hljs-comment"># Replace with your base64-encoded AWS secret key</span>
</code></pre>
<h4 id="heading-step-2-define-the-cronjob">Step 2: Define the CronJob</h4>
<p>Create a CronJob YAML file to schedule nightly backups:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">batch/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">CronJob</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">backup-job</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">schedule:</span> <span class="hljs-string">"0 0 * * *"</span>  <span class="hljs-comment"># Runs at midnight every day</span>
  <span class="hljs-attr">jobTemplate:</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">template:</span>
        <span class="hljs-attr">spec:</span>
          <span class="hljs-attr">containers:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">pg-bkup</span>
            <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/pg-bkup</span>
            <span class="hljs-attr">command:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">/bin/sh</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">-c</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">backup</span> <span class="hljs-string">--storage</span> <span class="hljs-string">s3</span> <span class="hljs-string">--path</span> <span class="hljs-string">/backups</span>
            <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_PORT</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"5432"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_HOST</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"postgres"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_NAME</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"mydb"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_USERNAME</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">backup-secret</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">username</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_PASSWORD</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">backup-secret</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">password</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_S3_ENDPOINT</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://s3.amazonaws.com"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_S3_BUCKET_NAME</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"mybucket"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_REGION</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"us-east-1"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_DISABLE_SSL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"false"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_FORCE_PATH_STYLE</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"false"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_ACCESS_KEY_ID</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">backup-secret</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">aws_access_key_id</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_SECRET_ACCESS_KEY</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">backup-secret</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">aws_secret_access_key</span>
          <span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">Never</span>
</code></pre>
<h4 id="heading-optional-enable-backup-encryption">Optional: Enable Backup Encryption</h4>
<p>PG-BKUP supports GPG encryption for added security. To encrypt your backups, include the <code>GPG_PASSPHRASE</code> or <code>GPG_PUBLIC_KEY</code> environment variable in the CronJob configuration.</p>
<hr />
<h3 id="heading-2-backup-multiple-databases-using-a-deployment">2. Backup Multiple Databases Using a Deployment</h3>
<p>If you’re managing multiple databases, you can use a single Kubernetes Deployment to back them up. Here’s how:</p>
<h4 id="heading-step-1-create-a-configmap">Step 1: Create a ConfigMap</h4>
<p>Define the configuration for multiple databases in a ConfigMap:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ConfigMap</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">backup-config</span>
<span class="hljs-attr">data:</span>
  <span class="hljs-attr">config.yaml:</span> <span class="hljs-string">|
    cronExpression: "@daily"  # Optional: Schedule backups daily
    databases:
      - host: postgres1
        port: 5432
        name: database1
        user: database1
        password: password
        path: /s3-path/database1
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">postgres2</span>
        <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">lldap</span>
        <span class="hljs-attr">user:</span> <span class="hljs-string">lldap</span>
        <span class="hljs-attr">password:</span> <span class="hljs-string">password</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/s3-path/lldap</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">postgres3</span>
        <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">keycloak</span>
        <span class="hljs-attr">user:</span> <span class="hljs-string">keycloak</span>
        <span class="hljs-attr">password:</span> <span class="hljs-string">password</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/s3-path/keycloak</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">postgres4</span>
        <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">joplin</span>
        <span class="hljs-attr">user:</span> <span class="hljs-string">joplin</span>
        <span class="hljs-attr">password:</span> <span class="hljs-string">password</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/s3-path/joplin</span>
</code></pre>
<h4 id="heading-step-2-define-the-deployment">Step 2: Define the Deployment</h4>
<p>Create a Deployment YAML file to manage the backups:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">backups</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">backups</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">backups</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">pg-bkup</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/pg-bkup</span>
        <span class="hljs-attr">command:</span> [<span class="hljs-string">"backup"</span>, <span class="hljs-string">"--storage"</span>, <span class="hljs-string">"s3"</span>, <span class="hljs-string">"--path"</span>, <span class="hljs-string">"/backups"</span>, <span class="hljs-string">"--config"</span>, <span class="hljs-string">"/config/config.yaml"</span>]
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">limits:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"128Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"500m"</span>
        <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_S3_ENDPOINT</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://s3.amazonaws.com"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_S3_BUCKET_NAME</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"mybucket"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_REGION</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"us-east-1"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_DISABLE_SSL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"false"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_FORCE_PATH_STYLE</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"false"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_ACCESS_KEY_ID</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">backup-secret</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">aws_access_key_id</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_SECRET_ACCESS_KEY</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">backup-secret</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">aws_secret_access_key</span>
        <span class="hljs-attr">volumeMounts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">config</span>
          <span class="hljs-attr">mountPath:</span> <span class="hljs-string">"/config"</span>
          <span class="hljs-attr">readOnly:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">config</span>
        <span class="hljs-attr">configMap:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">backup-config</span>
</code></pre>
<h4 id="heading-optional-enable-notifications">Optional: Enable Notifications</h4>
<p>If you enable <code>backupRescueMode</code> (which allows backups to proceed even if one database is down), consider setting up notifications to alert you of any backup failures.</p>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>Automating Postgres database backups in Kubernetes using <strong>PG-BKUP</strong> is a robust and efficient way to ensure your data is secure and recoverable. With support for multiple storage options (local, S3, SFTP, and Azure Blob) and GPG encryption, PG-BKUP provides unparalleled flexibility and security. By implementing these strategies, you can safeguard your critical data and minimize downtime in case of unexpected failures.</p>
<p>For more detailed instructions and advanced configurations, explore the official documentation: <a target="_blank" href="https://jkaninda.github.io/pg-bkup/">PG-BKUP Documentation</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Simplify Backing Up Multiple MySQL Databases on Docker]]></title><description><![CDATA[Backing up multiple databases on Docker can be challenging, especially when you need to handle each database individually. Ensuring your data is backed up regularly is crucial to prevent data loss and maintain business continuity. 
This guide provide...]]></description><link>https://blog.jkaninda.dev/simplify-backing-up-multiple-mysql-databases-on-docker</link><guid isPermaLink="true">https://blog.jkaninda.dev/simplify-backing-up-multiple-mysql-databases-on-docker</guid><category><![CDATA[MySQL]]></category><category><![CDATA[mysqldump]]></category><category><![CDATA[mysql-buckup]]></category><category><![CDATA[Docker]]></category><category><![CDATA[docker images]]></category><dc:creator><![CDATA[Jonas Kaninda]]></dc:creator><pubDate>Sat, 25 Jan 2025 13:50:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737812738164/e84dcffa-d6a6-4785-84f2-561bf6e252bc.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Backing up multiple databases on Docker can be challenging, especially when you need to handle each database individually. Ensuring your data is backed up regularly is crucial to prevent data loss and maintain business continuity. </p>
<p>This guide provides a comprehensive walkthrough on automating backups for multiple MySQL databases running inside Docker containers using the <strong>Mysql-bkup</strong> tool. With Mysql-bkup, you can streamline the process, making it easier and more efficient to manage backups for multiple databases simultaneously.</p>
<h2 id="heading-why-backing-up-multiple-databases-is-challenging"><strong>Why Backing Up Multiple Databases is Challenging</strong></h2>
<ul>
<li><strong>Complexity</strong>: Managing individual backup scripts for each database can be time-consuming and error-prone.</li>
<li><strong>Resource Management</strong>: Running multiple backup processes simultaneously can strain system resources.</li>
<li><strong>Consistency</strong>: Ensuring all databases are backed up at the same time and with the same settings can be difficult.</li>
</ul>
<h2 id="heading-how-mysql-bkup-simplifies-the-process"><strong>How Mysql-bkup Simplifies the Process</strong></h2>
<p><strong>Mysql-bkup</strong> is a Docker-based tool designed to <strong>backup, restore, and migrate MySQL databases</strong>. It supports multiple databases, and storage options (local, S3, SFTP, Azure Blob), and ensures data security through GPG encryption. With its configuration file, you can define backup schedules and settings for multiple databases in one place, eliminating the need for individual scripts.</p>
<h2 id="heading-key-benefits-of-using-mysql-bkup"><strong>Key Benefits of Using Mysql-bkup</strong></h2>
<ul>
<li><strong>Centralized Configuration</strong>: Manage backups for multiple databases using a single configuration file.</li>
<li><strong>Flexible Scheduling</strong>: Set up global or database-specific backup schedules with cron expressions.</li>
<li><strong>Multiple Storage Options</strong>: Store backups locally or in the cloud (S3, Azure Blob, SFTP).</li>
<li><strong>Security</strong>: Encrypt backups using GPG for added protection.</li>
<li><strong>Notifications</strong>: Receive email or Telegram alerts for backup success or failure.</li>
</ul>
<h2 id="heading-getting-started"><strong>Getting Started</strong></h2>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Before proceeding, ensure the following are in place:</p>
<ul>
<li>Docker installed and running.</li>
<li>MySQL or MariaDB databases running in Docker containers.</li>
</ul>
<h2 id="heading-automating-backups-for-multiple-databases"><strong>Automating Backups for Multiple Databases</strong></h2>
<p>Mysql-bkup allows you to configure and automate backups for multiple databases using a <strong>configuration file</strong>. This file defines the backup settings for each database, including host, port, credentials, and storage paths.</p>
<h2 id="heading-configuration-file-setup">Configuration File Setup</h2>
<p>The configuration file can be mounted into the container at <code>/config/config.yaml</code>, <code>/config/config.yml</code>, or specified via the <code>BACKUP_CONFIG_FILE</code> environment variable.</p>
<h3 id="heading-key-features">Key Features:</h3>
<ul>
<li><strong>Global Environment Variables</strong>: Use these for databases that share the same configuration.</li>
<li><strong>Database-Specific Overrides</strong>: Override global settings for individual databases by specifying them in the configuration file or using the database name as a suffix in the variable name (e.g., <code>DB_HOST_DATABASE1</code>).</li>
<li><strong>Global Cron Expression</strong>: Define a global <code>cronExpression</code> in the configuration file to schedule backups for all databases. If omitted, backups will run immediately.</li>
<li><strong>Configuration File Path</strong>: Specify the configuration file path using:<ul>
<li>The <code>BACKUP_CONFIG_FILE</code> environment variable.</li>
<li>The <code>--config</code> or <code>-c</code> flag for the backup command.</li>
</ul>
</li>
</ul>
<h3 id="heading-example-configuration-file-configyaml"><strong>Example Configuration File (<code>config.yaml</code>)</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># Optional: Define a global cron expression for scheduled backups.</span>
<span class="hljs-comment"># Example: "@every 20m" (runs every 20 minutes). If omitted, backups run immediately.</span>
<span class="hljs-attr">cronExpression:</span> <span class="hljs-string">"@midnight"</span> <span class="hljs-comment"># Optional</span>

<span class="hljs-attr">databases:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">mysql1</span>       <span class="hljs-comment"># Optional: Overrides DB_HOST or uses DB_HOST_DATABASE1.</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">3306</span>         <span class="hljs-comment"># Optional: Default is 3306. Overrides DB_PORT or uses DB_PORT_DATABASE1.</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">database1</span>    <span class="hljs-comment"># Required: Database name.</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">database1</span>    <span class="hljs-comment"># Optional: Overrides DB_USERNAME or uses DB_USERNAME_DATABASE1.</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">password</span> <span class="hljs-comment"># Optional: Overrides DB_PASSWORD or uses DB_PASSWORD_DATABASE1.</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/s3-path/database1</span>  <span class="hljs-comment"># Required: Backup path for SSH, FTP, or S3 (e.g., /home/toto/backup/).</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">mysql2</span>       <span class="hljs-comment"># Optional: Overrides DB_HOST or uses DB_HOST_LLAP.</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">3306</span>         <span class="hljs-comment"># Optional: Default is 3306. Overrides DB_PORT or uses DB_PORT_LLAP.</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">lldap</span>        <span class="hljs-comment"># Required: Database name.</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">lldap</span>        <span class="hljs-comment"># Optional: Overrides DB_USERNAME or uses DB_USERNAME_LLAP.</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">password</span> <span class="hljs-comment"># Optional: Overrides DB_PASSWORD or uses DB_PASSWORD_LLAP.</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/s3-path/lldap</span>  <span class="hljs-comment"># Required: Backup path for SSH, FTP, or S3 (e.g., /home/toto/backup/).</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">mysql3</span>       <span class="hljs-comment"># Optional: Overrides DB_HOST or uses DB_HOST_KEYCLOAK.</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">3306</span>         <span class="hljs-comment"># Optional: Default is 3306. Overrides DB_PORT or uses DB_PORT_KEYCLOAK.</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">keycloak</span>     <span class="hljs-comment"># Required: Database name.</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">keycloak</span>     <span class="hljs-comment"># Optional: Overrides DB_USERNAME or uses DB_USERNAME_KEYCLOAK.</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">password</span> <span class="hljs-comment"># Optional: Overrides DB_PASSWORD or uses DB_PASSWORD_KEYCLOAK.</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/s3-path/keycloak</span>  <span class="hljs-comment"># Required: Backup path for SSH, FTP, or S3 (e.g., /home/toto/backup/).</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">mysql4</span>       <span class="hljs-comment"># Optional: Overrides DB_HOST or uses DB_HOST_JOPLIN.</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">3306</span>         <span class="hljs-comment"># Optional: Default is 3306. Overrides DB_PORT or uses DB_PORT_JOPLIN.</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">joplin</span>       <span class="hljs-comment"># Required: Database name.</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">joplin</span>       <span class="hljs-comment"># Optional: Overrides DB_USERNAME or uses DB_USERNAME_JOPLIN.</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">password</span> <span class="hljs-comment"># Optional: Overrides DB_PASSWORD or uses DB_PASSWORD_JOPLIN.</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/s3-path/joplin</span>  <span class="hljs-comment"># Required: Backup path for SSH, FTP, or S3 (e.g., /home/toto/backup/).</span>
</code></pre>
<h3 id="heading-deploying-with-docker-compose"><strong>Deploying with Docker Compose</strong></h3>
<p>To deploy the backup solution using Docker Compose, create a <code>compose.yaml</code> file with the following content:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">mysql-bkup:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/mysql-bkup</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">mysql-bkup</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">backup</span> <span class="hljs-string">--config</span> <span class="hljs-string">/backup/config.yaml</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./backup:/backup</span>  <span class="hljs-comment"># Mount the backup directory</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./config.yaml:/backup/config.yaml</span>  <span class="hljs-comment"># Mount the configuration file</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">web</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">web:</span>
</code></pre>
<hr />
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Backing up multiple MySQL databases on Docker doesn’t have to be complicated. With <strong>Mysql-bkup</strong>, you can automate and streamline the process, ensuring your data is secure and easily recoverable. Whether you’re managing a single database or multiple, this tool provides the flexibility and reliability you need.</p>
<p>For more detailed instructions and advanced configurations, explore the official documentation: <a target="_blank" href="https://jkaninda.github.io/mysql-bkup/">Mysql-bkup Documentation</a>. Start automating your backups today and enjoy peace of mind knowing your data is protected.</p>
]]></content:encoded></item><item><title><![CDATA[Backing Up MySQL Databases Made Easy on Docker with  Mysql-bkup]]></title><description><![CDATA[Introduction
Backing up your data is essential, especially when managing databases in Docker environments. Regular backups of your MySQL database can prevent data loss and ensure business continuity. This guide provides a comprehensive walkthrough on...]]></description><link>https://blog.jkaninda.dev/backing-up-mysql-databases-made-easy-on-docker-with-mysql-bkup</link><guid isPermaLink="true">https://blog.jkaninda.dev/backing-up-mysql-databases-made-easy-on-docker-with-mysql-bkup</guid><category><![CDATA[mysql-buckup]]></category><category><![CDATA[mysql-s3-backup]]></category><category><![CDATA[mysql-azure-backup]]></category><category><![CDATA[Docker]]></category><category><![CDATA[mysqldump]]></category><dc:creator><![CDATA[Jonas Kaninda]]></dc:creator><pubDate>Sat, 25 Jan 2025 13:17:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718527167092/89bcf5b7-8f1e-401c-a192-aaf29e0c7a59.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p>Backing up your data is essential, especially when managing databases in Docker environments. Regular backups of your MySQL database can prevent data loss and ensure business continuity. This guide provides a comprehensive walkthrough on automating nightly backups for a MySQL database running inside a Docker container using the <a target="_blank" href="https://github.com/jkaninda/mysql-bkup">Mysql-bkup</a> tool.</p>
<p><strong>Mysql-bkup</strong> is a Docker container image designed to <strong>backup, restore, and migrate MySQL databases</strong>. It supports multiple storage options, including local storage, S3, SFTP, and Azure Blob, and ensures data security through GPG encryption.</p>
<hr />
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before proceeding, ensure the following are in place:</p>
<ul>
<li>Docker installed and running.</li>
<li>A MySQL or MariaDB database running in a Docker container.</li>
<li>(Optional) S3-compatible storage, SFTP, or Azure Blob storage for remote backups.</li>
</ul>
<hr />
<h2 id="heading-backup-methods"><strong>Backup Methods</strong></h2>
<h3 id="heading-1-backup-using-local-storage"><strong>1. Backup Using Local Storage</strong></h3>
<p>This method binds local storage to the Docker container for database backups.</p>
<h4 id="heading-method-1-using-docker-cli"><strong>Method 1: Using Docker CLI</strong></h4>
<pre><code class="lang-bash">docker run --rm --network your_network_name \
  -v <span class="hljs-variable">$PWD</span>/backup:/backup/ \
  -e <span class="hljs-string">"DB_HOST=dbhost"</span> \
  -e <span class="hljs-string">"DB_USERNAME=username"</span> \
  -e <span class="hljs-string">"DB_PASSWORD=password"</span> \
  -e <span class="hljs-string">"DB_PORT=3306"</span> \
  jkaninda/mysql-bkup backup -d database_name
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li>The command runs a new Docker container on the same network as the MySQL database container.</li>
<li>The backup is saved to a local directory (<code>./backup</code>) on the host machine.</li>
<li>Ensure the <code>mysql-bkup</code> container is connected to the same Docker network as your database to avoid connectivity issues.</li>
</ul>
<h4 id="heading-method-2-using-docker-compose"><strong>Method 2: Using Docker Compose</strong></h4>
<p>Create a <code>compose.yaml</code> file with the following content:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">mysql-bkup:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/mysql-bkup</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">mysql-bkup</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">backup</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./backup:/backup</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PORT=3306</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_NAME=database</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_USERNAME=username</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PASSWORD=password</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">web</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">web:</span>
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li>The <code>mysql-bkup</code> container is configured to back up the specified database.</li>
<li>The backup is stored in the <code>./backup</code> directory on the host machine.</li>
</ul>
<h4 id="heading-method-3-recurring-backups"><strong>Method 3: Recurring Backups</strong></h4>
<p>Mysql-bkup supports scheduled backups using cron expressions. You can use predefined schedules or custom intervals.</p>
<p><strong>Predefined Schedules:</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Entry</td><td>Description</td><td>Equivalent To</td></tr>
</thead>
<tbody>
<tr>
<td><code>@yearly</code></td><td>Run once a year, midnight, Jan. 1st</td><td><code>0 0 1 1 *</code></td></tr>
<tr>
<td><code>@monthly</code></td><td>Run once a month, midnight</td><td><code>0 0 1 * *</code></td></tr>
<tr>
<td><code>@weekly</code></td><td>Run once a week, midnight</td><td><code>0 0 * * 0</code></td></tr>
<tr>
<td><code>@daily</code></td><td>Run once a day, midnight</td><td><code>0 0 * * *</code></td></tr>
<tr>
<td><code>@hourly</code></td><td>Run once an hour</td><td><code>0 * * * *</code></td></tr>
<tr>
<td><code>@every "duration"</code></td><td>Run on fixed intervals ( <code>@every 1h20m</code>)</td><td><code>-</code></td></tr>
</tbody>
</table>
</div><p><strong>Example: Recurring Backup with Docker Compose</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">mysql-bkup:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/mysql-bkup</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">mysql-bkup</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">backup</span> <span class="hljs-string">-d</span> <span class="hljs-string">database</span> <span class="hljs-string">-e</span> <span class="hljs-string">@midnight</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./backup:/backup</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PORT=3306</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_NAME=database</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_USERNAME=username</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PASSWORD=password</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">web</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">web:</span>
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li>The <code>-e @midnight</code> flag schedules a daily backup at midnight.</li>
<li>Backups are stored in the <code>./backup</code> directory.</li>
</ul>
<hr />
<h3 id="heading-2-backup-using-s3-storage"><strong>2. Backup Using S3 Storage</strong></h3>
<p>Mysql-bkup supports S3-compatible storage for backups. Use the <code>--storage s3</code> flag to enable this feature.</p>
<p><strong>Example: S3 Backup with Docker Compose</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">mysql-bkup:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/mysql-bkup</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">mysql-bkup</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">backup</span> <span class="hljs-string">--storage</span> <span class="hljs-string">s3</span> <span class="hljs-string">-d</span> <span class="hljs-string">database</span> <span class="hljs-string">--cron-expression</span> <span class="hljs-string">"0 1 * * *"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PORT=3306</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_NAME=database</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_USERNAME=username</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PASSWORD=password</span>
      <span class="hljs-comment">## AWS Configuration</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AWS_S3_ENDPOINT=https://s3.amazonaws.com</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AWS_S3_BUCKET_NAME=backup</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AWS_REGION=us-west-2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AWS_ACCESS_KEY=xxxx</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AWS_SECRET_KEY=xxxxx</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AWS_DISABLE_SSL="false"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AWS_FORCE_PATH_STYLE=false</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">web</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">web:</span>
</code></pre>
<p><strong>Required Environment Variables:</strong></p>
<ul>
<li><code>AWS_S3_ENDPOINT</code>: S3 endpoint URL (e.g., <code>https://s3.amazonaws.com</code>).</li>
<li><code>AWS_S3_BUCKET_NAME</code>: Name of the S3 bucket.</li>
<li><code>AWS_REGION</code>: AWS region (e.g., <code>us-west-2</code>).</li>
<li><code>AWS_ACCESS_KEY</code>: AWS access key.</li>
<li><code>AWS_SECRET_KEY</code>: AWS secret key.</li>
<li><code>AWS_DISABLE_SSL</code>: Set to <code>"true"</code> for S3 alternatives without SSL.</li>
<li><code>AWS_FORCE_PATH_STYLE</code>: Set to <code>"true"</code> for S3 alternatives like Minio.</li>
</ul>
<hr />
<h3 id="heading-3-backup-using-azure-blob-storage"><strong>3. Backup Using Azure Blob Storage</strong></h3>
<p>Mysql-bkup also supports Azure Blob storage. Use the <code>--storage azure</code> flag to enable this feature.</p>
<p><strong>Example: Azure Blob Backup with Docker Compose</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">mysql-bkup:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/mysql-bkup</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">mysql-bkup</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">backup</span> <span class="hljs-string">--storage</span> <span class="hljs-string">azure</span> <span class="hljs-string">-d</span> <span class="hljs-string">database</span> <span class="hljs-string">--path</span> <span class="hljs-string">your-custom-path</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PORT=3306</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_NAME=database</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_USERNAME=username</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PASSWORD=password</span>
      <span class="hljs-comment">## Azure Blob Configuration</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AZURE_STORAGE_CONTAINER_NAME=backup-container</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AZURE_STORAGE_ACCOUNT_NAME=account-name</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AZURE_STORAGE_ACCOUNT_KEY=your-account-key</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">web</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">web:</span>
</code></pre>
<p><strong>Required Environment Variables:</strong></p>
<ul>
<li><code>AZURE_STORAGE_CONTAINER_NAME</code>: Name of the Azure Blob container.</li>
<li><code>AZURE_STORAGE_ACCOUNT_NAME</code>: Azure Storage account name.</li>
<li><code>AZURE_STORAGE_ACCOUNT_KEY</code>: Azure Storage account key.</li>
</ul>
<hr />
<h2 id="heading-notifications"><strong>Notifications</strong></h2>
<p>Mysql-bkup can send email or Telegram notifications for backup success or failure.</p>
<h3 id="heading-email-notifications"><strong>Email Notifications</strong></h3>
<p>Configure SMTP credentials to enable email notifications.</p>
<p><strong>Example: Email Notification Configuration</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">mysql-bkup:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/mysql-bkup</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">mysql-bkup</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">backup</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./backup:/backup</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PORT=3306</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_NAME=database</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_USERNAME=username</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PASSWORD=password</span>
      <span class="hljs-comment">## SMTP Configuration</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MAIL_HOST=smtp.example.com</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MAIL_PORT=587</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MAIL_USERNAME=your-email@example.com</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MAIL_PASSWORD=your-email-password</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MAIL_FROM=Backup</span> <span class="hljs-string">Jobs</span> <span class="hljs-string">&lt;backup@example.com&gt;</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MAIL_TO=me@example.com,team@example.com</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MAIL_SKIP_TLS=false</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TIME_FORMAT=2006-01-02</span> <span class="hljs-string">at</span> <span class="hljs-number">15</span><span class="hljs-string">:04:05</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">BACKUP_REFERENCE=database/Paris</span> <span class="hljs-string">cluster</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">web</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">web:</span>
</code></pre>
<hr />
<h3 id="heading-telegram-notifications"><strong>Telegram Notifications</strong></h3>
<p>Provide your Telegram bot token and chat ID to enable Telegram notifications.</p>
<p><strong>Example: Telegram Notification Configuration</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">mysql-bkup:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jkaninda/mysql-bkup</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">mysql-bkup</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">backup</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./backup:/backup</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PORT=3306</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_NAME=database</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_USERNAME=username</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PASSWORD=password</span>
      <span class="hljs-comment">## Telegram Configuration</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TG_TOKEN=[BOT</span> <span class="hljs-string">ID]:[BOT</span> <span class="hljs-string">TOKEN]</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TG_CHAT_ID=your-chat-id</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TIME_FORMAT=2006-01-02</span> <span class="hljs-string">at</span> <span class="hljs-number">15</span><span class="hljs-string">:04:05</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">BACKUP_REFERENCE=database/Paris</span> <span class="hljs-string">cluster</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">web</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">web:</span>
</code></pre>
<hr />
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Automating MySQL database backups in Docker using <strong>Mysql-bkup</strong> is a robust and efficient way to ensure your data is secure and recoverable. 
With support for multiple storage options <strong>local, S3, SFTP, and Azure Blob</strong> this tool provides unparalleled flexibility. 
The added layer of GPG encryption ensures your backups remain secure, while customizable notifications keep you informed about backup successes or failures. 
By implementing these strategies, you can safeguard your critical data and minimize downtime in case of unexpected failures.</p>
<p>For more detailed instructions and advanced configurations, explore the official documentation: <a target="_blank" href="https://jkaninda.github.io/mysql-bkup/">Mysql-bkup Documentation</a>. Start automating your backups today and enjoy peace of mind knowing your data is protected. </p>
]]></content:encoded></item></channel></rss>