<?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[Ever Gauzy]]></title><description><![CDATA[Ever Gauzy]]></description><link>https://blog.gauzy.co</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 19:31:22 GMT</lastBuildDate><atom:link href="https://blog.gauzy.co/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Setup Ever Gauzy Platform on Digital Ocean Kubernetes]]></title><description><![CDATA[Introduction
In this tutorial, we will guide you step by step on how to set up Ever Gauzy in K8S on Digital Ocean.
Ever Gauzy is an open Business Management Platform. Here are some features you can find in Gauzy:

Human Resources Management (HRM) wit...]]></description><link>https://blog.gauzy.co/setup-ever-gauzy-platform-on-digital-ocean-kubernetes</link><guid isPermaLink="true">https://blog.gauzy.co/setup-ever-gauzy-platform-on-digital-ocean-kubernetes</guid><dc:creator><![CDATA[Ruslan Konviser]]></dc:creator><pubDate>Fri, 28 Feb 2025 12:02:53 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>In this tutorial, we will guide you step by step on how to set up Ever Gauzy in K8S on Digital Ocean.</p>
<p>Ever Gauzy is an open Business Management Platform. Here are some features you can find in Gauzy:</p>
<ul>
<li><p>Human Resources Management (HRM) with Time Management / Tracking and Employees Performance Monitoring</p>
</li>
<li><p>Customer Relationship Management (CRM)</p>
</li>
<li><p>Enterprise Resource Planning (ERP)</p>
</li>
<li><p>Projects / Tasks Management</p>
</li>
<li><p>Sales Management</p>
</li>
<li><p>Financial and Cost Management (including <em>Accounting</em>, <em>Invoicing</em>, etc)</p>
</li>
<li><p>Inventory, Supply Chain Management, and Production Management</p>
</li>
<li><p>Etc.</p>
</li>
</ul>
<p>For more information about Gauzy, visit <a target="_blank" href="https://gauzy.co">https://gauzy.co</a>.</p>
<h1 id="heading-prerequisites"><strong>Prerequisites</strong></h1>
<h2 id="heading-create-a-new-digitalocean-account">Create a New DigitalOcean Account</h2>
<p>If you've already done this, you can skip this step. If not, you have the option to use either the official method or our referral link: <a target="_blank" href="https://m.do.co/c/70545a065ad4">https://m.do.co/c/70545a065ad4</a>.</p>
<p>If you decide to use a referral link, you should see a page similar to the one shown below. Choose your preferred signup method.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740731286411/f4c399ef-aa7b-47db-ad14-5d59db6b0d1a.png" alt class="image--center mx-auto" /></p>
<p>Here are a couple of other things you'll need throughout this guide:</p>
<ul>
<li><p>A domain name and DNS A records which you can point to the DigitalOcean Load Balancer used by the Ingress.</p>
</li>
<li><p><code>kubectl</code>: A command-line tool used to control and manage Kubernetes clusters.</p>
</li>
<li><p><code>git</code>: A version control system that helps track changes in files, especially source code.</p>
</li>
<li><p><code>helm</code> : A package manager for Kubernetes.</p>
</li>
</ul>
<h1 id="heading-setup-gauzy-with-do-kubernetes">Setup Gauzy with DO <strong>Kubernetes</strong></h1>
<h2 id="heading-step-1-create-postgresql-db">Step 1. Create PostgreSQL DB</h2>
<p>To create a PostgreSQL database in Digital Ocean, go to your dashboard and under the "Manage" option, click on the "Databases" menu, you should see a "Create Database" button, as shown in the screenshot below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740731544109/2e3fd2c8-1635-48ee-afc2-cd570f09793c.png" alt class="image--center mx-auto" /></p>
<p>After clicking on "Create Database," you'll reach the "Create Database Cluster" page, where you'll set up your database.</p>
<ul>
<li><p>First, choose the database region. You may want to select the datacenter region closest to your location. In my case, I decided to go with '<strong>San Francisco <em>Database 2</em> SF02.</strong>'</p>
</li>
<li><p>In the "<strong>Choose a Database Engine</strong>" section, select the Postgres option. We recommend running the latest PostgreSQL (16.x) and enabling connection pooling for production workloads.</p>
</li>
</ul>
<blockquote>
<p>Please note that the region where we create our database should be the same as the region where we create our Kubernetes Cluster. We will cover this in the next step.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740731923424/e00d1772-5c61-4761-a5d9-60531d0f2dd7.png" alt class="image--center mx-auto" /></p>
<p>Let's choose a unique database cluster name. I'll name it <code>ever-gauzy-db-demo</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740732067187/0660cf74-1fa9-4024-a2a3-69a45727bb3c.png" alt class="image--center mx-auto" /></p>
<p>You can either leave the other fields at their default values or customize them based on your requirements. Then, click on "<strong><em>Create Database Cluster</em></strong>" button to set up your database.</p>
<p>After clicking the button, you will be directed to the our Database page.</p>
<p>As shown in the image below, our database is still in the creation process. Your database will be ready once the blue progress bar reaches the end.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740733545888/147bfbf5-4a06-4538-b32e-b811487b3085.png" alt class="image--center mx-auto" /></p>
<p>While the database server is being created, let's copy our connection details.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740733676472/8537b3f6-bd6f-4c28-ac32-a1ab9eab80a8.png" alt class="image--center mx-auto" /></p>
<p>Copy and save our <strong><em>connection details (VPC Network)</em></strong> in a secure location, also we need to download the <strong>CA certificate</strong>.</p>
<p>We will need these details later when setting up Gauzy API in Kubernetes .</p>
<h2 id="heading-step-2-create-kubernetes-cluster">Step 2. Create Kubernetes Cluster</h2>
<p>Go to your dashboard and under the "Manage" option, click on the "Kubernetes" menu, you should see a "Create Cluster" button, as shown in the screenshot below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740734454799/741119e3-243b-4d9d-ac2f-9671bb615530.png" alt class="image--center mx-auto" /></p>
<p>After clicking on "Create Cluster," you'll reach the "Create a Kubernetes cluster" page, where you'll set up your K8s Cluster.</p>
<blockquote>
<p>Ensure it operates in the same region as your database, as outlined in the previous step.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740734508281/6ecbf706-8cd2-4706-8c63-cefa65a84789.png" alt class="image--center mx-auto" /></p>
<p>Set the nodes size of your cluster, We recommend running at least 2 node clusters, each node with 8Gb RAM or more.</p>
<blockquote>
<p>A minimum of 2 nodes is required to prevent downtime during upgrades or maintenance.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740734621759/f7f21e7f-edcb-4924-ac36-4f23a30af123.png" alt class="image--center mx-auto" /></p>
<p>Provide a name for our Cluster and write it down, as we'll need to update it in our codebase while deploying the platform.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740734728004/385f3264-3f13-4b65-b234-2880d12f93c0.png" alt class="image--center mx-auto" /></p>
<p>You can either leave the other fields at their default values or customize them based on your requirements.</p>
<p>Click the <code>Create Cluster</code> button to create your K8S cluster.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740734749160/4e5e1b28-0c72-49fb-8f4e-385e5fca9b2e.png" alt class="image--center mx-auto" /></p>
<p>While the cluster is being created, let's install kubectl, which is a command-line tool used to control and manage Kubernetes clusters.</p>
<h2 id="heading-step-3-install-kubectl">Step 3: Install <code>kubectl</code></h2>
<p>Before we start deploying our platform (Gauzy), we will need kubectl to deploy our platform's K8s manifest.</p>
<p>kubectl is the command-line tool for interacting with Kubernetes clusters. It allows you to deploy and manage applications, inspect cluster resources, view logs, and perform other administrative tasks.</p>
<p>Please refer to the following links to install kubectl.</p>
<ul>
<li><p><a target="_blank" href="https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/">Install kubectl on Linux</a></p>
</li>
<li><p><a target="_blank" href="https://kubernetes.io/docs/tasks/tools/install-kubectl-macos/">Install kubectl on macOS</a></p>
</li>
<li><p><a target="_blank" href="https://kubernetes.io/docs/tasks/tools/install-kubectl-windows/">Install kubectl on Windows</a></p>
</li>
</ul>
<p>After completing the installation, run the following command to verify.</p>
<pre><code class="lang-bash">kubectl version

<span class="hljs-comment"># Client Version: v1.32.1</span>
<span class="hljs-comment"># Kustomize Version: v5.5.0</span>
<span class="hljs-comment"># Server Version: v1.31.1</span>
</code></pre>
<h2 id="heading-step-4-download-the-kube-config-file-for-our-k8s-cluster">Step 4: Download the Kube Config File for Our K8S Cluster</h2>
<p>To be able to interact with our k8s cluster we need to download the cluster config file from our Digital ocean cluster interface.</p>
<p>Return to the Kubernetes menu under the "Manage" option, and click on the cluster we previously created. In the Overview section, you should see a button to download the configuration file, as illustrated in the image below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740734814411/904c3234-85f7-4658-89fa-8ef0cb23bc1f.png" alt class="image--center mx-auto" /></p>
<p>Store it at <code>~/kube/gauzy-k8s-demo-kubeconfig.yaml</code> (You can name the file as you prefer). This file contains essential information about the Kubernetes cluster, such as the server URL, authentication details, and context.</p>
<p>By default, kubectl looks for a file named <code>config</code> in the <code>$HOME/.kube</code> directory. You can specify other kubeconfig files by setting the <code>KUBECONFIG</code> environment variable.</p>
<ol>
<li>Export the kubeconfig path:</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> KUBECONFIG=~/.kube/gauzy-k8s-demo-kubeconfig.yaml
</code></pre>
<ol>
<li>With the kubeconfig variable set, you open your terminal and run the command:</li>
</ol>
<pre><code class="lang-bash">kubectl get nodes
</code></pre>
<p>This command, reaches out to the Kubernetes API server specified in the kubeconfig file. It authenticates using the provided credentials and retrieves information about the nodes in the cluster.</p>
<p>Your output should be similar to the following:</p>
<pre><code class="lang-plaintext">NAME                   STATUS   ROLES    AGE   VERSION
pool-edj6rnz5y-at6lf   Ready    &lt;none&gt;   4h   v1.32.1
pool-edj6rnz5y-at6lq   Ready    &lt;none&gt;   4h   v1.32.1
</code></pre>
<h2 id="heading-step-5-install-helm">Step 5: Install <code>helm</code></h2>
<p>Helm is a package manager for Kubernetes that helps you manage complex applications. Think of it like apt/yum/homebrew but for Kubernetes applications.</p>
<p>We need this tool to deploy Traefik; we'll discuss it further in the next step.</p>
<p>Learn how to install Helm by visiting the following link:</p>
<ul>
<li><a target="_blank" href="https://helm.sh/docs/intro/install">https://helm.sh/docs/intro/install</a></li>
</ul>
<p>After completing the installation, run the following command to verify.</p>
<pre><code class="lang-bash">helm version

<span class="hljs-comment"># version.BuildInfo{Version:"v3.17.1", GitCommit:"980d8ac1939e39138101364400756af2bdee1da5", GitTreeState:"clean", GoVersion:"go1.23.5"}</span>
</code></pre>
<h2 id="heading-step-6-using-a-load-balancer-with-traefik">Step 6: Using a Load Balancer with Traefik</h2>
<p><a target="_blank" href="https://traefik.io/traefik/">Traefik</a> is a modern HTTP reverse proxy and load balancer for Kubernetes that handles incoming traffic to your services. It automatically discovers services running in Kubernetes and creates the routing configuration for them.</p>
<p>In this section, you’ll install Traefik into your cluster and prepare it to be used with the certificates managed by cert-manager. We will also set up a load balancer, which will send incoming network traffic to your Traefik service from outside your cluster.</p>
<p>First, you’ll need to add the <code>traefik</code> Helm repository to your available repositories, which will allow Helm to find the <code>traefik</code> package:</p>
<pre><code class="lang-bash">helm repo add traefik https://traefik.github.io/charts
</code></pre>
<p>Once the command completes, you’ll receive confirmation that the <code>traefik</code> repository has been added to your computer’s Helm repositories:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Output</span>
<span class="hljs-string">"traefik"</span> has been added to your repositories
</code></pre>
<p>Next, update your chart repositories:</p>
<pre><code class="lang-bash">helm repo update
</code></pre>
<p>The output will confirm that the <code>traefik</code> chart repository has been updated:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Ouput</span>
Hang tight <span class="hljs-keyword">while</span> we grab the latest from your chart repositories...
...Successfully got an update from the <span class="hljs-string">"traefik"</span> chart repository
Update Complete. ⎈Happy Helming!⎈
</code></pre>
<p>Finally, install <code>traefik</code> into the <code>traefik</code> namespace you created in your cluster:</p>
<pre><code class="lang-bash">helm install traefik traefik/traefik
</code></pre>
<p>First, <code>helm install</code> tells Helm that you want to install a new application. The next word <code>traefik</code> is just a name you're giving to this specific installation - you could actually name it anything you want. This name helps you reference this specific installation later if you need to upgrade or uninstall it.</p>
<p>After that, <code>traefik/traefik</code> identifies which application you're installing. The first <code>traefik</code> refers to the Helm repository, while the second refers to the actual chart name.</p>
<p>Once you run the command, output similar to the following will print to the screen:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Ouput</span>
NAME: traefik
LAST DEPLOYED: Mon Feb 24 06:38:16 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
traefik with docker.io/traefik:v3.3.3 has been deployed successfully on default namespace !
</code></pre>
<blockquote>
<p>Please note that the previous command will install Traefik in the default namespace.</p>
</blockquote>
<p>Once the Helm chart is installed, Traefik will begin downloading on your cluster. To see whether Traefik is up and running, run <code>kubectl get all</code> to see all the Traefik resources created:</p>
<pre><code class="lang-bash">kubectl get all
</code></pre>
<p>Your output will appear similar to the output below:</p>
<pre><code class="lang-plaintext">NAME                           READY   STATUS    RESTARTS   AGE
pod/traefik-6ff695b798-d4j8j   1/1     Running   0          3m13s

NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)                      AGE
service/kubernetes   ClusterIP      10.109.0.1     &lt;none&gt;         443/TCP                      3d22h
service/traefik      LoadBalancer   10.109.1.155   **138.68.39.63**   80:31023/TCP,443:30257/TCP   3m16s

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/traefik   1/1     1            1           3m14s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/traefik-6ff695b798   1         1         1       3m15s
</code></pre>
<p>Depending on your cluster and when you ran the previous command, some of the names and ages may be different. If you see <code>&lt;pending&gt;</code> under <code>EXTERNAL-IP</code> for your <code>service/traefik</code>, keep running the <code>kubectl get -n traefik all</code> command until an IP address is listed. The <code>EXTERNAL-IP</code> is the IP address the load balancer is available from on the internet. Once an IP address is listed, make note of that IP address as your <code>traefik_ip_address</code>. You’ll use this address in the next section to set up your domain.</p>
<p>In this section, you installed Traefik into your cluster and have an <code>EXTERNAL-IP</code> you can direct your website traffic to. In the next section, you’ll make the changes to your DNS to send traffic from your domain to the load balancer.</p>
<h2 id="heading-step-7-accessing-traefik-with-your-domain">Step 7: Accessing Traefik with Your Domain</h2>
<p>Now that you have Traefik set up in your cluster and accessible on the internet with a load balancer, you'll need to update your domain's DNS to point to your Traefik load balancer.</p>
<p>You will need to create two DNS <code>A</code> records, both pointing to your Traefik loadBalancer's <code>EXTERNAL-IP</code> address that you noted in the previous step:</p>
<ul>
<li><p><code>api.your-domain.com</code>: This record will direct traffic to the Gauzy API service</p>
</li>
<li><p><code>app.your-domain.com</code>: This record will direct traffic to the Gauzy web application</p>
</li>
</ul>
<p>These DNS records will allow users to access both the API and web application through their respective subdomains. The Traefik load balancer will then handle routing the traffic to the appropriate service within your Kubernetes cluster based on the incoming request's hostname.</p>
<p>Make sure to replace <code>your-domain.com</code> with your actual domain name when creating these records in your DNS provider's configuration panel. After creating the records, it may take some time (usually between a few minutes to 48 hours) for the DNS changes to propagate across the internet.</p>
<h2 id="heading-step-8-setting-up-cert-manager-in-your-cluster">Step 8: Setting Up cert-manager in Your Cluster</h2>
<p>Now that you have your DNS records configured to point to your Traefik load balancer, now we are setting up SSL certificates to ensure secure communication for our Gauzy services.</p>
<p>Traditionally, when setting up secure certificates for a website, you would need to generate a <a target="_blank" href="https://en.wikipedia.org/wiki/Certificate_signing_request">certificate signing request</a> and pay a trusted <a target="_blank" href="https://en.wikipedia.org/wiki/Certificate_authority">certificate authority</a> to generate a certificate for you. You would then need to configure your web server to use that certificate and remember to go through that same process every year to keep your certificates up-to-date.</p>
<p>However, with the creation of <a target="_blank" href="https://letsencrypt.org/">Let’s Encrypt</a> in 2014, it's now possible to acquire free certificates through an automated process. These certificates are only valid for a few months instead of a year, though, so using an automated system to renew those certificates is a requirement. To handle that, you'll use cert-manager, a service designed to run in Kubernetes that automatically manages the lifecycle of your certificates. This will ensure that both your Gauzy API (<code>api.your-domain.com</code>) and web application (<code>app.your-domain.com</code>) have valid SSL certificates that are automatically renewed when needed.</p>
<p>In this section, you will set up cert-manager to run in your cluster in its own <code>cert-manager</code> namespace.</p>
<p>First, <a target="_blank" href="https://cert-manager.io/docs/installation/">install cert-manager</a> <a target="_blank" href="https://cert-manager.io/docs/installation/">using <code>kubectl</code> with c</a>ert-manager’s release file:</p>
<pre><code class="lang-bash">kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml
</code></pre>
<p>By default, cert-manager wil<a target="_blank" href="https://cert-manager.io/docs/installation/">l install in its own</a> namespace named <code>cert-manager</code>. As the file is applied, a number of resources will be created in your cluster, which will appear in your output (some of the output is removed due to length):</p>
<pre><code class="lang-bash"><span class="hljs-comment"># OUTPUT</span>
namespace/cert-manager created
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created

<span class="hljs-comment"># some output excluded</span>

deployment.apps/cert-manager-webhook created
mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
</code></pre>
<p>To verify our installation, check the <code>cert-manager</code> Namespace for running pods:</p>
<pre><code class="lang-bash">kubectl get pods --namespace cert-manager
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># Output</span>
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-7979fbf6b6-twpg6              1/1     Running   0          15m
cert-manager-cainjector-68b64d44c7-t87t7   1/1     Running   0          15m
cert-manager-webhook-ff897cd5d-rznw6       1/1     Running   0          15m
</code></pre>
<p>This indicates that the cert-manager installation succeeded.</p>
<p>Now we need to create an Issuer, which specifies the certificate authority from which signed x509 certificates can be obtained. In this guide, we’ll use the Let’s Encrypt certificate authority, which provides free TLS certificates and offers both a staging server for testing your certificate configuration, and a production server for rolling out verifiable TLS certificates.</p>
<p>Let’s create a test ClusterIssuer to make sure the certificate provisioning mechanism is functioning correctly. A ClusterIssuer is not namespace-scoped and can be used by <a target="_blank" href="https://cert-manager.io/docs/concepts/certificate/">Certificate</a> resources in any namespace.</p>
<p>Open a file named <code>staging_issuer.yaml</code> in your favorite text editor:</p>
<pre><code class="lang-bash">nano staging_issuer.yaml
</code></pre>
<p>Paste in the following ClusterIssuer manifest:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">cert-manager.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ClusterIssuer</span>
<span class="hljs-attr">metadata:</span>
 <span class="hljs-attr">name:</span> <span class="hljs-string">letsencrypt-staging</span>
 <span class="hljs-attr">namespace:</span> <span class="hljs-string">cert-manager</span>
<span class="hljs-attr">spec:</span>
 <span class="hljs-attr">acme:</span>
   <span class="hljs-comment"># The ACME server URL</span>
   <span class="hljs-attr">server:</span> <span class="hljs-string">https://acme-staging-v02.api.letsencrypt.org/directory</span>
   <span class="hljs-comment"># Email address used for ACME registration</span>
   <span class="hljs-attr">email:</span> <span class="hljs-string">**your_email_address_here**</span>
   <span class="hljs-comment"># Name of a secret used to store the ACME account private key</span>
   <span class="hljs-attr">privateKeySecretRef:</span>
     <span class="hljs-attr">name:</span> <span class="hljs-string">letsencrypt-staging</span>
   <span class="hljs-comment"># Enable the HTTP-01 challenge provider</span>
   <span class="hljs-attr">solvers:</span>
   <span class="hljs-bullet">-</span> <span class="hljs-attr">http01:</span>
       <span class="hljs-attr">ingress:</span>
         <span class="hljs-attr">class:</span> <span class="hljs-string">traefik</span>
</code></pre>
<p>Here we specify that we’d like to create a ClusterIssuer called <code>letsencrypt-staging</code>, and use the Let’s Encrypt staging server. We’ll later use the production server to roll out our certificates, but the production server rate-limits requests made against it, so for testing purposes you should use the staging URL.</p>
<p>We then specify an email address to register the certificate, and create a Kubernetes <a target="_blank" href="https://kubernetes.io/docs/concepts/configuration/secret/">Secret</a> called <code>letsencrypt-staging</code> to store the ACME account’s private key. We also use the <code>HTTP-01</code> challenge mechanism. To learn more about these parameters, consult the official cert-manager documentation on <a target="_blank" href="https://cert-manager.io/docs/concepts/issuer/">Issuers</a>.</p>
<p>Roll out the ClusterIssuer using <code>kubectl</code>:</p>
<pre><code class="lang-bash">kubectl create -f staging_issuer.yaml
</code></pre>
<p>You should see the following output:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Output</span>
clusterissuer.cert-manager.io/letsencrypt-staging created
</code></pre>
<p>We’ll now repeat this process to create the production ClusterIssuer. Note that certificates will only be created after annotating and updating the Ingress resource provisioned in the previous step.</p>
<p>Open a file called <code>prod_issuer.yaml</code> in your favorite editor:</p>
<pre><code class="lang-bash">nano prod_issuer.yaml
</code></pre>
<p>Paste in the following manifest:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">cert-manager.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ClusterIssuer</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">letsencrypt-prod</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">cert-manager</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">acme:</span>
    <span class="hljs-comment"># The ACME server URL</span>
    <span class="hljs-attr">server:</span> <span class="hljs-string">https://acme-v02.api.letsencrypt.org/directory</span>
    <span class="hljs-comment"># Email address used for ACME registration</span>
    <span class="hljs-attr">email:</span> <span class="hljs-string">**your_email_address_here**</span>
    <span class="hljs-comment"># Name of a secret used to store the ACME account private key</span>
    <span class="hljs-attr">privateKeySecretRef:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">letsencrypt-prod</span>
    <span class="hljs-comment"># Enable the HTTP-01 challenge provider</span>
    <span class="hljs-attr">solvers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">http01:</span>
        <span class="hljs-attr">ingress:</span>
          <span class="hljs-attr">class:</span> <span class="hljs-string">traefik</span>
</code></pre>
<p>Note the different ACME server URL, and the <code>letsencrypt-prod</code> secret key name.</p>
<p>When you’re done editing, save and close the file.</p>
<p>Roll out this Issuer using <code>kubectl</code>:</p>
<pre><code class="lang-bash">kubectl create -f prod_issuer.yaml
</code></pre>
<p>You should see the following output:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Output</span>
clusterissuer.cert-manager.io/letsencrypt-prod created
</code></pre>
<p>With our Let's Encrypt staging and production ClusterIssuers set up, we're now ready to configure <strong>Gauzy Services</strong>. Next, we will create the Ingress Resource and enable TLS encryption for the paths <a target="_blank" href="http://api.your-domain.com"><code>api.your-domain.com</code></a> and <a target="_blank" href="http://app.your-domain.com"><code>app.your-domain.com</code></a>.</p>
<p>In the next section, we will set up the Gauzy API service and enable TLS encryption for API domain.</p>
<h2 id="heading-step-9-setup-gauzy-api-in-the-kubernetes-cluster"><strong>Step 9: Setup Gauzy API in the Kubernetes cluster.</strong></h2>
<p>As mentioned earlier, Gauzy operates through two main services: the API service (backend) and the WEBAPP service (frontend). These services correspond to the DNS records we created (<a target="_blank" href="http://api.your-domain.com"><code>api.your-domain.com</code></a> and <a target="_blank" href="http://app.your-domain.com"><code>app.your-domain.com</code></a> respectively). In this section, we'll focus on deploying the API service, which serves as the backend of our application, to our Kubernetes cluster.</p>
<p>The API service will handle all the business logic and data operations, while being securely exposed through Traefik and protected with automatically managed SSL certificates. Let's proceed with the deployment configuration.</p>
<p>Open a file called <code>gauzy-api.yaml</code> in your favorite editor:</p>
<pre><code class="lang-bash">nano gauzy-api.yaml
</code></pre>
<p>Paste in the following manifest:</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">gauzy-prod-api</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">gauzy-prod-api</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">gauzy-prod-api</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">gauzy-prod-api</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/ever-co/gauzy-api:latest</span>
          <span class="hljs-attr">resources:</span>
            <span class="hljs-attr">requests:</span>
              <span class="hljs-attr">memory:</span> <span class="hljs-string">"1536Mi"</span>
              <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1000m"</span>
            <span class="hljs-attr">limits:</span>
              <span class="hljs-attr">memory:</span> <span class="hljs-string">"2048Mi"</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">API_HOST</span>
              <span class="hljs-attr">value:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DEMO</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">NODE_ENV</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"production"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ADMIN_PASSWORD_RESET</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"true"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">LOG_LEVEL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"info"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">API_BASE_URL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://**api.your-domain.com**"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">CLIENT_BASE_URL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://**app.your-domain.com**"</span>

            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_TYPE</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_ORM"</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"typeorm"</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">"private-ever-gauzy-db-demo-do-user-8531843-0.f.db.ondigitalocean.com"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_SSL_MODE</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"true"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_CA_CERT</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">|
                LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVRVENDQXFtZ0F3SUJBZ0lVQzA5THo4WWVo
                ..........
                RGRaUEdjakRoWGdUY3RSYm5TZ0N1c1FFRXc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
</span>            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_USER</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"doadmin"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_PASS</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"********"</span> <span class="hljs-comment"># Your password here</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">"defaultdb"</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">"25060"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_POOL_SIZE</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"10"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_POOL_SIZE_KNEX</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"10"</span>

            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">CLOUD_PROVIDER</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"DO"</span>

            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">REDIS_ENABLED</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">DEFAULT_CURRENCY</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"USD"</span>

            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ALLOW_SUPER_ADMIN_ROLE</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"true"</span>

            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">FILE_PROVIDER</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"LOCAL"</span>

            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">GAUZY_AI_GRAPHQL_ENDPOINT</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://**api.your-domain.com**/graphql"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">GAUZY_AI_REST_ENDPOINT</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://**api.your-domain.com**/api"</span>

            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">MAGIC_CODE_EXPIRATION_TIME</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"600"</span>

            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">APP_NAME</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"Gauzy"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">APP_LOGO</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://**app.your-domain.com**/assets/images/logos/logo_Gauzy.png"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">APP_SIGNATURE</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"Gauzy"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">APP_LINK</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://**app.your-domain.com**"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">APP_EMAIL_CONFIRMATION_URL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://**app.your-domain.com**/#/auth/confirm-email"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">APP_MAGIC_SIGN_URL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://**app.your-domain.com**/#/auth/magic-sign-in"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">COMPANY_LINK</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://**your-company-domain.com**"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">COMPANY_NAME</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"**You Company Name**"</span>

          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">3000</span>
              <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
</code></pre>
<p>This Kubernetes manifest defines a Deployment for the Gauzy API service in a production environment. Here's a breakdown of its key components:</p>
<p>The deployment named <code>gauzy-prod-api</code> runs a single replica of the container using the latest Gauzy API image from GitHub Container Registry. It's configured with specific resource requirements: requesting 1.5GB of memory (with a 2GB limit) and 1 CPU core.</p>
<p>The container configuration includes numerous environment variables that determine how the API service operates:</p>
<ul>
<li><p>Base URLs are set to the domains we configured earlier (<a target="_blank" href="http://api.your-domain.com"><code>api.your-domain.com</code></a> and <a target="_blank" href="http://app.your-domain.com"><code>app.your-domain.com</code></a>)</p>
</li>
<li><p>Database configuration points to a PostgreSQL instance on DigitalOcean, including SSL settings</p>
</li>
<li><p>Application-specific settings like currency, admin access, and file storage</p>
</li>
<li><p>Various URLs for the application's frontend features (email confirmation, magic sign-in, etc.)</p>
</li>
</ul>
<p>The API service exposes port 3000 for TCP traffic, which will be the endpoint that Traefik routes traffic to when requests come to <a target="_blank" href="http://api.your-domain.com"><code>api.your-domain.com</code></a>.</p>
<p>You'll need to replace sensitive values like the database password and customize the domain names and company information before applying this manifest to your cluster:</p>
<ol>
<li>Update the database configuration to align with your database details established in the previous section (create database):</li>
</ol>
<pre><code class="lang-bash">- DB_HOST -&gt; coordinate with <span class="hljs-string">"host"</span> key
- DB_USER -&gt; coordinate with <span class="hljs-string">"username"</span> key
- DB_PASS -&gt; coordinate with <span class="hljs-string">"password"</span> key
- DB_NAME -&gt; coordinate with <span class="hljs-string">"port"</span> key
- DB_PORT -&gt; coordinate with <span class="hljs-string">"port"</span>
- DB_POOL_SIZE -&gt; The pool size we <span class="hljs-built_in">set</span> when creating our Connection Pool (10)
</code></pre>
<ol start="2">
<li>In the previous step (creating the database), we downloaded our database connection certificate, named <code>ca-certificate.crt</code>. We need to convert its content to base64 and assign it to the <code>DB_CA_CERT</code> variable. Run the following command to convert the certificate to base64 format:</li>
</ol>
<pre><code class="lang-bash">base64 ca-certificate.crt
</code></pre>
<p>You should see the Base64 value of our certificate file printed in the terminal. Copy this value and update the <code>DB_CA_CERT</code> variable with it.</p>
<ol start="3">
<li><p>Don’t forget to replace the following variables with your setup values.</p>
<ol>
<li><p><a target="_blank" href="http://api.your-domain.com"><strong>api.your-domain.com</strong></a> your API service domain.</p>
</li>
<li><p><a target="_blank" href="http://app.your-domain.com"><strong>app.your-domain.com</strong></a> your WebApp service domain.</p>
</li>
<li><p><a target="_blank" href="http://your-company-domain.com"><strong>your-company-domain.com</strong></a> your company domain name, if applicable.</p>
</li>
<li><p><strong>You Company Name</strong> your company name.</p>
</li>
</ol>
</li>
<li><p>You can find all available variables to include in your deployment here:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/ever-co/ever-gauzy/blob/develop/.env.local">GitHub Repo (.env.local)</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/ever-co/ever-gauzy/blob/develop/.deploy/k8s/k8s-manifest.prod.yaml#L69-L271">GitHub K8S Manifest</a></p>
</li>
</ul>
</li>
</ol>
<p>We assumed you are setting up a production deployment, but Gauzy provides different docker images for different environments such as Demo, Stage, and Production, You find them here: <a target="_blank" href="https://github.com/orgs/ever-co/packages?repo_name=ever-gauzy">Gauzy GitHub Packages</a>.</p>
<p>After creating the <code>gauzy-api.yaml</code> file and ensuring you have set valid values that correspond to your setup, let's apply our deployment to the Kubernetes cluster:</p>
<pre><code class="lang-bash">kubectl apply -f gauzy-api.yaml
</code></pre>
<p>This command applies the Kubernetes Deployment configuration defined in the <code>gauzy-api.yaml</code> file. It creates or updates the Gauzy API Deployment in your cluster.</p>
<p>You should see the following output:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Output</span>
deployment.apps/gauzy-prod-api created
</code></pre>
<p>Verifying the Deployment:</p>
<pre><code class="lang-bash">kubectl get deploy
</code></pre>
<p>Use this command to check the status of your Deployments. You should see the following output:</p>
<pre><code class="lang-bash">NAME             READY   UP-TO-DATE   AVAILABLE   AGE
gauzy-prod-api   1/1     1            1           17m
traefik          1/1     1            1           4h30m
</code></pre>
<ul>
<li><p>NAME: The name of the Deployment</p>
</li>
<li><p>READY: The number of ready replicas / total desired replicas</p>
</li>
<li><p>UP-TO-DATE: The number of replicas updated to the latest version</p>
</li>
<li><p>AVAILABLE: The number of replicas available to users</p>
</li>
<li><p>AGE: How long the Deployment has been running</p>
</li>
</ul>
<p>Checking Deployment Logs:</p>
<pre><code class="lang-bash">kubectl logs deploy/gauzy-prod-api --tail 10
</code></pre>
<p>This command retrieves the last 10 lines of logs from the Gauzy API Deployment. It's useful for verifying successful startup or debugging issues.</p>
<p>You should see the following similar output:</p>
<pre><code class="lang-plaintext">[Nest] 1  - 02/24/2025, 9:00:35 AM     LOG [RouterExplorer] Mapped {/api/dashboard-widget/count, GET} route +0ms
[Nest] 1  - 02/24/2025, 9:00:35 AM     LOG [RouterExplorer] Mapped {/api/dashboard-widget/pagination, GET} route +0ms
...
Application is running on &lt;http://0.0.0.0:3000&gt;
Listening at &lt;http://0.0.0.0:3000/api&gt;
✔ API Running: 29.132s
✔ Total API Startup Time: 29.133s
</code></pre>
<p>This log output shows the API routes being mapped and confirms that the application has started successfully.</p>
<blockquote>
<p>Note: If you encounter errors, use the same <code>kubectl logs</code> command to view more detailed logs for troubleshooting. Adjust the --tail value or remove it entirely to see more log entries if needed.</p>
</blockquote>
<h3 id="heading-step-10-expose-the-gauzy-api-using-kubernetes-service-and-ingress">Step 10: Expose the Gauzy API using Kubernetes Service and Ingress.</h3>
<p>With our Gauzy API deployment set up, let's create a Kubernetes Service to expose the API within the cluster and potentially to external traffic. Here's how we can do that:</p>
<ol>
<li>Create a file named <code>gauzy-api-service.yaml</code> with the following content:</li>
</ol>
<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">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">gauzy-api-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">gauzy-prod-api</span> <span class="hljs-comment"># This is the name for our deployment specification: template -&gt; metadata -&gt; labels -&gt; app.</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
      <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">3000</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
</code></pre>
<ol start="2">
<li>Apply the Service configuration:</li>
</ol>
<pre><code class="lang-bash">kubectl apply -f gauzy-api-service.yaml
</code></pre>
<p>This Service will:</p>
<ul>
<li><p>Select all pods with the label app: <code>gauzy-prod-api</code></p>
</li>
<li><p>Forward traffic from port <code>80</code> to the container port <code>3000</code></p>
</li>
<li><p>Be accessible within the cluster using the service name <code>gauzy-api-service</code></p>
</li>
</ul>
<ol start="3">
<li>Verify the Service creation:</li>
</ol>
<pre><code class="lang-bash">kubectl get services
</code></pre>
<p>You should see your new service listed along with other existing services.</p>
<pre><code class="lang-plaintext">NAME                TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)                      AGE
gauzy-api-service   ClusterIP      10.109.3.62    &lt;none&gt;         80/TCP                       5m
kubernetes          ClusterIP      10.109.0.1     &lt;none&gt;         443/TCP                      4d7h
traefik             LoadBalancer   10.109.1.155   138.68.39.63   80:31023/TCP,443:30257/TCP   9h
</code></pre>
<p>With this API Service in place, other components within your Kubernetes cluster can now communicate with the Gauzy API using the service name <code>gauzy-api-service</code>.</p>
<p>Now let's create an Ingress based on Traefik to expose our API service to be accessed externally:</p>
<ol>
<li>Create a file named <code>gauzy-api-ingress.yaml</code> with the following content:</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">networking.k8s.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Ingress</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">gauzy-api-ingress</span>
  <span class="hljs-attr">annotations:</span>
    <span class="hljs-attr">kubernetes.io/ingress.class:</span> <span class="hljs-string">traefik</span>
    <span class="hljs-attr">traefik.ingress.kubernetes.io/router.entrypoints:</span> <span class="hljs-string">websecure</span>
    <span class="hljs-attr">traefik.ingress.kubernetes.io/router.tls:</span> <span class="hljs-string">"true"</span>
    <span class="hljs-attr">cert-manager.io/cluster-issuer:</span> <span class="hljs-string">letsencrypt-prod</span>

<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">ingressClassName:</span> <span class="hljs-string">traefik</span>
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">api.your-domain.com</span>
      <span class="hljs-attr">http:</span>
        <span class="hljs-attr">paths:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
            <span class="hljs-attr">pathType:</span> <span class="hljs-string">Prefix</span>
            <span class="hljs-attr">backend:</span>
              <span class="hljs-attr">service:</span>
                <span class="hljs-attr">name:</span> <span class="hljs-string">gauzy-api-service</span> <span class="hljs-comment"># Previously created our service name.</span>
                <span class="hljs-attr">port:</span>
                  <span class="hljs-attr">number:</span> <span class="hljs-number">80</span>
  <span class="hljs-attr">tls:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">secretName:</span> <span class="hljs-string">letsencrypt-cert-api</span> <span class="hljs-comment"># Unique Secret for Each Ingress</span>
      <span class="hljs-attr">hosts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">api.your-domain.com</span>
</code></pre>
<p>Remember to replace <a target="_blank" href="http://api.your-domain.com"><code>api.your-domain.com</code></a> with your actual domain in the Ingress configuration.</p>
<ol start="2">
<li>Apply the Ingress configuration:</li>
</ol>
<pre><code class="lang-bash">kubectl apply -f gauzy-api-ingress.yaml
</code></pre>
<p>This Ingress will:</p>
<ul>
<li><p>Uses Traefik as the ingress controller.</p>
</li>
<li><p>It's configured for HTTPS (websecure entrypoint) with TLS enabled.</p>
</li>
<li><p>The <a target="_blank" href="http://cert-manager.io/cluster-issuer"><code>cert-manager.io/cluster-issuer</code></a><code>: letsencrypt-prod</code> annotation specifies the cluster-issuer, which should correspond to one of the issuers created in a previous step.</p>
</li>
<li><p>It routes traffic for <a target="_blank" href="http://api.your-domain.com"><code>api.your-domain.com</code></a> to the <code>gauzy-api-service</code> on port 80.</p>
</li>
<li><p>The <a target="_blank" href="http://traefik.ingress.kubernetes.io/router.entrypoints"><code>traefik.ingress.kubernetes.io/router.entrypoints</code></a> annotation tells Traefik that traffic for this <code>Ingress</code> should be available via the <code>websecure</code> entrypoint. This is an entrypoint the Helm chart configures by default to handle HTTPS traffic and listens on <code>traefik_ip_address</code> port <code>443</code>, the default for HTTPS.</p>
</li>
</ul>
<ol start="3">
<li>Verify the Ingress creation:</li>
</ol>
<pre><code class="lang-bash">kubectl get ingress
</code></pre>
<p>You should see the following similar output:</p>
<pre><code class="lang-bash">NAME                CLASS    HOSTS                    ADDRESS        PORTS     AGE
gauzy-api-ingress   traefik  api.your-domain.com      138.68.39.63   80, 443   2m34s
</code></pre>
<ol start="4">
<li><p>Verify the Gauzy API via Browser:</p>
<p> After successfully creating our Service and Ingress, we can now interact with the Gauzy API. Open your Gauzy API domain (<a target="_blank" href="https://api.your-domain.com/api"><code>https://api.your-domain.com/api</code></a>) in a browser; you should see a successful result, as shown in the picture below.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740740875124/4421396b-857a-47c9-96c1-1fc4b5472115.png" alt class="image--center mx-auto" /></p>
<p>In this section, we created the Kubernetes Service to expose the API within the cluster, allowing the Ingress Resource to redirect external traffic to it.</p>
<h2 id="heading-step-11-setup-gauzy-webapp-in-the-kubernetes-cluster"><strong>Step 11: Setup Gauzy WEBAPP in the Kubernetes cluster.</strong></h2>
<p>With the Gauzy API set up, let's proceed to configure Gauzy Web within the Kubernetes cluster.</p>
<p>The webapp service will serve the Gauzy frontend application, providing the user interface and interacting with the API service. Like the API, it will be securely exposed through Traefik and protected with automatically managed SSL certificates. This setup ensures a secure and efficient delivery of the Gauzy web application to end-users.</p>
<p>Let's proceed with the deployment configuration for the webapp service, following a similar pattern to what we've done for the API, but tailored for frontend hosting requirements.</p>
<p>Open a file called <code>gauzy-webapp.yaml</code> in your favorite editor:</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">gauzy-prod-webapp</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">gauzy-prod-webapp</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">gauzy-prod-webapp</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">gauzy-prod-webapp</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/ever-co/gauzy-webapp:latest</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DEMO</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">API_BASE_URL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://api.**your-domain.com**"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">CLIENT_BASE_URL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"https://app.**your-domain.com**"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DEFAULT_CURRENCY</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"USD"</span>

          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">4200</span>
              <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
</code></pre>
<p>This Kubernetes manifest defines a Deployment for the Gauzy WEBAPP service in a production environment. Here's a breakdown of its key components:</p>
<p>The deployment named <code>gauzy-prod-webapp</code> runs a single replica of the container using the latest Gauzy API image from GitHub Container Registry.</p>
<p>The container configuration includes numerous environment variables that determine how the API service operates:</p>
<ul>
<li><p>Base URLs are set to the domains we configured earlier (<code>api.your-domain.com</code> and <code>app.your-domain.com</code>)</p>
</li>
<li><p>Application-specific settings like currency.</p>
</li>
</ul>
<p>The API service exposes port <code>4200</code> for TCP traffic, which will be the endpoint that Traefik routes traffic to when requests come to <code>app.your-domain.com</code>.</p>
<p>Don’t forget to replace the following variables with your setup values.</p>
<ol>
<li><p><a target="_blank" href="http://api.your-domain.com"><strong>api.your-domain.com</strong></a> your API service domain.</p>
</li>
<li><p><a target="_blank" href="http://app.your-domain.com"><strong>app.your-domain.com</strong></a> your WebApp service domain.</p>
</li>
</ol>
<p>You can find all available variables to include in your deployment here:</p>
<ul>
<li><a target="_blank" href="https://github.com/ever-co/ever-gauzy/blob/develop/.deploy/k8s/k8s-manifest.prod.yaml#L294-L332">GitHub K8S Manifest</a></li>
</ul>
<p>We assumed you are setting up a production deployment, but Gauzy provides different docker images for different environments such as Demo, Stage, and Production, You find them here: <a target="_blank" href="https://github.com/orgs/ever-co/packages?repo_name=ever-gauzy">Gauzy GitHub Packages</a>.</p>
<p>After creating the <code>gauzy-webapp.yaml</code> file and ensuring you have set valid values that correspond to your setup, let's apply our deployment to the Kubernetes cluster:</p>
<pre><code class="lang-yaml"><span class="hljs-string">kubectl</span> <span class="hljs-string">apply</span> <span class="hljs-string">-f</span> <span class="hljs-string">gauzy-webapp.yaml</span>
</code></pre>
<p>This command applies the Kubernetes Deployment configuration defined in the <code>gauzy-webapp.yaml</code> file. It creates or updates the Gauzy WEBAPP Deployment in your cluster.</p>
<p>You should see the following output:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Output</span>
deployment.apps/gauzy-prod-webapp created
</code></pre>
<p>Verifying the Deployment:</p>
<pre><code class="lang-bash">kubectl get deploy
</code></pre>
<p>Use this command to check the status of your Deployments. You should see the following similar output:</p>
<pre><code class="lang-bash">NAME                READY   UP-TO-DATE   AVAILABLE   AGE
gauzy-prod-api      1/1     1            1           4h42m
gauzy-prod-webapp   1/1     1            1           2m18s
traefik             1/1     1            1           8h
</code></pre>
<ul>
<li><p>NAME: The name of the Deployment</p>
</li>
<li><p>READY: The number of ready replicas / total desired replicas</p>
</li>
<li><p>UP-TO-DATE: The number of replicas updated to the latest version</p>
</li>
<li><p>AVAILABLE: The number of replicas available to users</p>
</li>
<li><p>AGE: How long the Deployment has been running</p>
</li>
</ul>
<p>Step 12: Expose the Gauzy WEBAPP using Kubernetes Service and Ingress.</p>
<p>With our Gauzy WEBAPP deployment set up, let's create a Kubernetes Service to expose the WEBAPP within the cluster and potentially to external traffic. Here's how we can do that:</p>
<ol>
<li>Create a file named <code>gauzy-webapp-service.yaml</code> with the following content:</li>
</ol>
<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">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">gauzy-webapp-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">gauzy-prod-webapp</span> <span class="hljs-comment"># This is the name for our deployment specification: template -&gt; metadata -&gt; labels -&gt; app.</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
      <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">4200</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
</code></pre>
<ol start="2">
<li>Apply the Service configuration:</li>
</ol>
<pre><code class="lang-bash">kubectl apply -f gauzy-webapp-service.yaml
</code></pre>
<p>This Service will:</p>
<ul>
<li><p>Select all pods with the label app: <code>gauzy-prod-webapp</code></p>
</li>
<li><p>Forward traffic from port <code>80</code> to the container port <code>4200</code></p>
</li>
<li><p>Be accessible within the cluster using the service name <code>gauzy-webapp-service</code></p>
</li>
</ul>
<ol start="3">
<li>Verify the Service creation:</li>
</ol>
<pre><code class="lang-bash">kubectl get services
</code></pre>
<p>You should see your new service listed along with other existing services.</p>
<pre><code class="lang-plaintext">NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)                      AGE
gauzy-api-service      ClusterIP      10.109.3.62    &lt;none&gt;         80/TCP                       128m
gauzy-webapp-service   ClusterIP      10.109.27.44   &lt;none&gt;         80/TCP                       5s
kubernetes             ClusterIP      10.109.0.1     &lt;none&gt;         443/TCP                      4d7h
traefik                LoadBalancer   10.109.1.155   138.68.39.63   80:31023/TCP,443:30257/TCP   9h
</code></pre>
<p>With this WEBAPP Service in place, other components within your Kubernetes cluster can now communicate with the Gauzy WEBAPP using the service name gauzy-webapp-service.</p>
<p>Now let's create an Ingress based on Traefik to expose our API service to be accessed externally:</p>
<ol>
<li>Create a file named <code>gauzy-webapp-ingress.yaml</code> with the following content:</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">networking.k8s.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Ingress</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">gauzy-webapp-ingress</span>
  <span class="hljs-attr">annotations:</span>
    <span class="hljs-attr">kubernetes.io/ingress.class:</span> <span class="hljs-string">traefik</span>
    <span class="hljs-attr">traefik.ingress.kubernetes.io/router.entrypoints:</span> <span class="hljs-string">websecure</span>
    <span class="hljs-attr">traefik.ingress.kubernetes.io/router.tls:</span> <span class="hljs-string">"true"</span>
    <span class="hljs-attr">cert-manager.io/cluster-issuer:</span> <span class="hljs-string">letsencrypt-prod</span>

<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">ingressClassName:</span> <span class="hljs-string">traefik</span>
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">app.your-domain.com</span>
      <span class="hljs-attr">http:</span>
        <span class="hljs-attr">paths:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
            <span class="hljs-attr">pathType:</span> <span class="hljs-string">Prefix</span>
            <span class="hljs-attr">backend:</span>
              <span class="hljs-attr">service:</span>
                <span class="hljs-attr">name:</span> <span class="hljs-string">gauzy-webapp-service</span> <span class="hljs-comment"># Previously created our service name.</span>
                <span class="hljs-attr">port:</span>
                  <span class="hljs-attr">number:</span> <span class="hljs-number">80</span>
  <span class="hljs-attr">tls:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">secretName:</span> <span class="hljs-string">letsencrypt-cert-webapp</span> <span class="hljs-comment"># Unique Secret for Each Ingress</span>
      <span class="hljs-attr">hosts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">app.your-domain.com</span>
</code></pre>
<p>Remember to replace <a target="_blank" href="http://app.your-domain.com"><code>app.your-domain.com</code></a> with your actual domain in the Ingress configuration.</p>
<ol start="2">
<li>Apply the Ingress configuration:</li>
</ol>
<pre><code class="lang-bash">kubectl apply -f gauzy-webapp-ingress.yaml
</code></pre>
<p>This Ingress will:</p>
<ul>
<li><p>Uses Traefik as the ingress controller.</p>
</li>
<li><p>It's configured for HTTPS (websecure entrypoint) with TLS enabled.</p>
</li>
<li><p>The <a target="_blank" href="http://cert-manager.io/cluster-issuer"><code>cert-manager.io/cluster-issuer</code></a><code>: letsencrypt-prod</code> annotation specifies the cluster-issuer, which should correspond to one of the issuers created in a previous step.</p>
</li>
<li><p>It routes traffic for <a target="_blank" href="http://app.your-domain.com"><code>app.your-domain.com</code></a> to the <code>gauzy-webapp-service</code> on port 80.</p>
</li>
<li><p>The <a target="_blank" href="http://traefik.ingress.kubernetes.io/router.entrypoints"><code>traefik.ingress.kubernetes.io/router.entrypoints</code></a> annotation tells Traefik that traffic for this <code>Ingress</code> should be available via the <code>websecure</code> entrypoint. This is an entrypoint the Helm chart configures by default to handle HTTPS traffic and listens on <code>traefik_ip_address</code> port <code>443</code>, the default for HTTPS.</p>
</li>
</ul>
<ol start="3">
<li>Verify the Ingress creation:</li>
</ol>
<pre><code class="lang-bash">kubectl get ingress
</code></pre>
<p>You should see the following similar output:</p>
<pre><code class="lang-plaintext">NAME                   CLASS     HOSTS                     ADDRESS        PORTS  AGE
gauzy-api-ingress      traefik   api.your-domain.com   138.68.39.63   80, 443    99m
gauzy-webapp-ingress   traefik   app.your-domain.com   138.68.39.63   80, 443    12s
</code></pre>
<ol start="4">
<li><p>Verify the Gauzy API via Browser:</p>
<p> After successfully creating our Service and Ingress, we can now interact with the Gauzy API. Open your Gauzy API domain (<a target="_blank" href="https://app.your-domain.com"><code>https://app.your-domain.com</code></a>) in a browser; you should see the login page displayed as shown in the image below.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740741694377/c5d61027-7440-4ba8-839d-4f436894e6e2.png" alt class="image--center mx-auto" /></p>
<p>In this section, we created the Kubernetes Service to expose the WEBAPP within the cluster, allowing the Ingress Resource to redirect external traffic to it.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congratulations! You have successfully set up Ever Gauzy on a Kubernetes cluster in DigitalOcean. Let's recap the key steps we've covered:</p>
<ol>
<li><p>Created a Kubernetes cluster on Digital Ocean</p>
</li>
<li><p>Created Managed database on Digital Ocean</p>
</li>
<li><p>Set up kubectl to manage your cluster</p>
</li>
<li><p>Installed and configured Traefik as an ingress controller</p>
</li>
<li><p>Deployed Ever Gauzy components (API and web app) to your cluster</p>
</li>
<li><p>Configured DNS settings to route traffic to your application</p>
</li>
</ol>
<p>By following this guide, you've deployed a scalable, production-ready instance of Ever Gauzy.</p>
<p>This setup leverages the power of Kubernetes for orchestration and Traefik for efficient traffic routing, all hosted on DigitalOcean's reliable infrastructure.</p>
<p>Some key benefits of this deployment method include:</p>
<ul>
<li><p>Scalability: Easily scale your application by adjusting the number of replicas in your Kubernetes deployments.</p>
</li>
<li><p>Reliability: Kubernetes ensures high availability by automatically managing and replacing unhealthy pods.</p>
</li>
<li><p>Flexibility: This setup allows for easy updates and maintenance of your Ever Gauzy instance.</p>
</li>
<li><p>Cost-effective: DigitalOcean provides a cost-efficient platform for hosting Kubernetes clusters.</p>
</li>
</ul>
<p>Remember to regularly update your Ever Gauzy components and Kubernetes configurations to ensure you're running the latest versions with the most up-to-date features and security patches.</p>
<p>For ongoing management, make sure to:</p>
<ul>
<li><p>Monitor your cluster's health and performance</p>
</li>
<li><p>Regularly back up your data</p>
</li>
<li><p>Keep your Kubernetes version updated</p>
</li>
<li><p>Stay informed about Ever Gauzy updates and new features</p>
</li>
</ul>
<p>With this setup, you're well-positioned to leverage Ever Gauzy for your business needs while benefiting from the robustness and flexibility of a Kubernetes-based deployment on DigitalOcean.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://gauzy.co/">https://gauzy.co</a></p>
</li>
<li><p><a target="_blank" href="https://www.digitalocean.com/">https://www.digitalocean.com</a></p>
</li>
<li><p><a target="_blank" href="https://m.do.co/c/70545a065ad4">https://m.do.co/c/70545a065ad4</a></p>
</li>
</ul>
<ul>
<li><p><a target="_blank" href="https://github.com/ever-co/ever-gauzy/?tab=readme-ov-file#-contact-us">https://github.com/ever-co/ever-gauzy/?tab=readme-ov-file#-contact-us</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=vfz1gQiM-VU">https://www.youtube.com/watch?v=vfz1gQiM-VU</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/ever-co/ever-gauzy/wiki/Setup-Gauzy-\(Self-Hosted\)">https://github.com/ever-co/ever-gauzy/wiki/Setup-Gauzy-(Self-Hosted)</a></p>
</li>
<li><p><a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-secure-your-site-in-kubernetes-with-cert-manager-traefik-and-let-s-encrypt">https://www.digitalocean.com/community/tutorials/how-to-secure-your-site-in-kubernetes-with-cert-manager-traefik-and-let-s-encrypt</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>