<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jaskaran Deogan</title>
    <description>The latest articles on DEV Community by Jaskaran Deogan (@jaskarandeogan).</description>
    <link>https://dev.to/jaskarandeogan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1048324%2F5a0ebd92-5e16-43c7-8667-7d1b8512fc17.jpeg</url>
      <title>DEV Community: Jaskaran Deogan</title>
      <link>https://dev.to/jaskarandeogan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jaskarandeogan"/>
    <language>en</language>
    <item>
      <title>Deploy n8n with Coolify: Self-Hosted Automation in Minutes</title>
      <dc:creator>Jaskaran Deogan</dc:creator>
      <pubDate>Mon, 13 Oct 2025 14:00:00 +0000</pubDate>
      <link>https://dev.to/jaskarandeogan/deploy-n8n-with-coolify-self-hosted-automation-in-minutes-457g</link>
      <guid>https://dev.to/jaskarandeogan/deploy-n8n-with-coolify-self-hosted-automation-in-minutes-457g</guid>
      <description>&lt;p&gt;Self-hosting n8n gives you unlimited workflow executions, full data control, and zero monthly fees. With Coolify, you can deploy a production-ready n8n instance in just a few clicks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Self-Host n8n?
&lt;/h3&gt;

&lt;p&gt;n8n is an open-source workflow automation tool that connects apps and services without code. Self-hosting means unlimited executions, complete data ownership, and significant cost savings at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Get with Coolify
&lt;/h3&gt;

&lt;p&gt;Coolify deploys n8n with a production-ready architecture including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n main instance&lt;/strong&gt; - The web interface and workflow editor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;n8n-worker&lt;/strong&gt; - Background execution for better performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; - Database for workflows and execution history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; - Message queue for distributed processing
Plus automatic SSL certificates, container management, and built-in monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deployment Steps
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;A Coolify instance running on your server&lt;/li&gt;
&lt;li&gt;A domain or subdomain pointed to your server (e.g., n8n.yourdomain.com)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 1: Create a New Project
&lt;/h4&gt;

&lt;p&gt;In your Coolify dashboard, create a new project to organize your n8n deployment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Add the n8n Service
&lt;/h4&gt;

&lt;p&gt;Select "Add New Resource" and choose the n8n service from Coolify's catalog. This automatically uses the Docker Compose configuration with all necessary services.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Configure Your Domain
&lt;/h4&gt;

&lt;p&gt;Set the SERVICE_URL_N8N environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SERVICE_URL_N8N=https://n8n.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells n8n where it's publicly accessible, which is essential for webhooks and external integrations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 4: Deploy and Restart
&lt;/h4&gt;

&lt;p&gt;Deploy the service, then restart it. This restart allows Coolify to generate SSL certificates and apply the domain configuration properly. Within moments, you'll have n8n running with HTTPS enabled automatically.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 5: Initial Setup and Activation
&lt;/h4&gt;

&lt;p&gt;Navigate to your n8n URL (e.g., &lt;a href="https://n8n.yourdomain.com):" rel="noopener noreferrer"&gt;https://n8n.yourdomain.com):&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Your Admin Account&lt;/strong&gt; - Set up your username and password&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Activation Popup&lt;/strong&gt; - n8n will display a popup asking you to activate your instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enter Your Email&lt;/strong&gt; - Provide your email address&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receive Your Key&lt;/strong&gt; - n8n will email you an activation key that unlocks community features&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The activation is optional but recommended for access to additional features like advanced permissions and extra integrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Your Instance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Logs&lt;/strong&gt;: View logs for all services directly from Coolify's dashboard.&lt;br&gt;
&lt;strong&gt;Scaling&lt;/strong&gt;: Add more worker instances as your automation needs grow.&lt;br&gt;
&lt;strong&gt;Backups&lt;/strong&gt;: Set up automated backups through Coolify to protect your workflows.&lt;br&gt;
&lt;strong&gt;Updates&lt;/strong&gt;: Pull the latest n8n image and redeploy when new versions are released.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Works
&lt;/h3&gt;

&lt;p&gt;Deploying n8n manually requires managing Docker files, configuring reverse proxies, and handling SSL certificates. Coolify handles all of that automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-click deployment with production-ready configuration&lt;/li&gt;
&lt;li&gt;Automatic HTTPS with Let's Encrypt&lt;/li&gt;
&lt;li&gt;Easy scaling and management&lt;/li&gt;
&lt;li&gt;All controls in one dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ready to automate? deploy n8n, and start connecting your tools in minutes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Resources:
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://coolify.io/docs/" rel="noopener noreferrer"&gt;Coolify Documentation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.n8n.io/" rel="noopener noreferrer"&gt;n8n Documentation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://community.n8n.io/" rel="noopener noreferrer"&gt;n8n Community Forum&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tooling</category>
      <category>lowcode</category>
      <category>agents</category>
    </item>
    <item>
      <title>Self-Hosting Listmonk with Coolify: Your Own Newsletter Platform in Minutes</title>
      <dc:creator>Jaskaran Deogan</dc:creator>
      <pubDate>Mon, 22 Sep 2025 00:24:21 +0000</pubDate>
      <link>https://dev.to/jaskarandeogan/self-hosting-listmonk-with-coolify-your-own-newsletter-platform-in-minutes-50j1</link>
      <guid>https://dev.to/jaskarandeogan/self-hosting-listmonk-with-coolify-your-own-newsletter-platform-in-minutes-50j1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhhquzqes56swroj29i7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhhquzqes56swroj29i7.png" alt="Dashboard Screenshot" width="800" height="488"&gt;&lt;/a&gt;Are you tired of paying monthly fees for newsletter services like Mailchimp or ConvertKit? Looking for complete control over your subscriber data and email campaigns? &lt;strong&gt;&lt;a href="https://listmonk.app/" rel="noopener noreferrer"&gt;Listmonk&lt;/a&gt;&lt;/strong&gt; might be exactly what you need.&lt;/p&gt;

&lt;p&gt;Listmonk is a powerful, self-hosted newsletter and mailing list manager that gives you all the features of premium services without the recurring costs or subscriber limits. Combined with &lt;strong&gt;Coolify&lt;/strong&gt; for easy deployment, you can have your own email marketing platform running in under 30 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Choose Listmonk?
&lt;/h2&gt;

&lt;p&gt;Before diving into the setup, let's look at why Listmonk stands out:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Cost-effective&lt;/strong&gt;: No monthly fees or subscriber limits&lt;br&gt;
✅ &lt;strong&gt;Privacy-focused&lt;/strong&gt;: Complete control over your data&lt;br&gt;
✅ &lt;strong&gt;Feature-rich&lt;/strong&gt;: Campaign analytics, segmentation, templates, and more&lt;br&gt;
✅ &lt;strong&gt;Modern UI&lt;/strong&gt;: Clean, intuitive interface that rivals paid services&lt;br&gt;
✅ &lt;strong&gt;API-first&lt;/strong&gt;: Integrate with your existing applications&lt;br&gt;
✅ &lt;strong&gt;Open source&lt;/strong&gt;: Actively maintained with a growing community&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A server or VPS with Docker support&lt;/li&gt;
&lt;li&gt;Coolify installed and configured&lt;/li&gt;
&lt;li&gt;A domain name pointed to your server&lt;/li&gt;
&lt;li&gt;SMTP credentials (Gmail, SendGrid, AWS SES, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Setup Process
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Create Your Listmonk Service in Coolify
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log into your Coolify dashboard&lt;/li&gt;
&lt;li&gt;Create a new project or use an existing one&lt;/li&gt;
&lt;li&gt;Add a new resource and choose "Docker Compose"&lt;/li&gt;
&lt;li&gt;Name your service (e.g., "listmonk-newsletter")&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Step 2: Configure Your Docker Compose
&lt;/h3&gt;

&lt;p&gt;Here's the complete &lt;code&gt;docker-compose.yml&lt;/code&gt; configuration that works perfectly with Coolify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;x-db-credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;listmonk/listmonk:latest'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk_app&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9000:9000'&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${SERVICE_FQDN_APP:-listmonk.example.com}'&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-c'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./listmonk&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--idempotent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--yes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--config&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;./listmonk&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--upgrade&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--yes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--config&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;./listmonk&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--config&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;''"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_app__address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0:9000'&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_app__root_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${LISTMONK_app__root_url}'&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_db__user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_db__password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_db__database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_db__host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_db__port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_db__ssl_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;disable&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_db__max_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_db__max_idle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_db__max_lifetime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;300s&lt;/span&gt;
      &lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Etc/UTC&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_ADMIN_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${LISTMONK_ADMIN_USER}'&lt;/span&gt;
      &lt;span class="na"&gt;LISTMONK_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${LISTMONK_ADMIN_PASSWORD}'&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./uploads:/listmonk/uploads:rw'&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CMD&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wget&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--no-verbose'&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--tries=1'&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--spider'&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:9000/'&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;40s&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;postgres:17-alpine'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk_db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CMD-SHELL&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;listmonk'&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;volume&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listmonk-data&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/postgresql/data&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;listmonk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;listmonk-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Set Environment Variables
&lt;/h3&gt;

&lt;p&gt;In your Coolify service settings, add these environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Required - Set your admin credentials&lt;/span&gt;
&lt;span class="nv"&gt;LISTMONK_ADMIN_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-admin-username
&lt;span class="nv"&gt;LISTMONK_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-secure-password

&lt;span class="c"&gt;# Required - Your domain configuration&lt;/span&gt;
&lt;span class="nv"&gt;SERVICE_FQDN_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;newsletter.yourdomain.com
&lt;span class="nv"&gt;LISTMONK_app__root_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://newsletter.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security Note&lt;/strong&gt;: Make sure to use a strong password for your admin account!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Deploy and Access
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Save your configuration in Coolify&lt;/li&gt;
&lt;li&gt;Deploy the service&lt;/li&gt;
&lt;li&gt;Wait for both containers to become healthy (usually 2-3 minutes)&lt;/li&gt;
&lt;li&gt;Access your Listmonk instance at &lt;code&gt;https://newsletter.yourdomain.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Initial Configuration
&lt;/h2&gt;

&lt;p&gt;Once Listmonk is running, you'll need to configure it:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. First Login
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to your domain&lt;/li&gt;
&lt;li&gt;Login with the credentials you set in environment variables&lt;/li&gt;
&lt;li&gt;You'll be greeted with the clean Listmonk dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Configure SMTP Settings
&lt;/h3&gt;

&lt;p&gt;Go to &lt;strong&gt;Settings &amp;gt; SMTP&lt;/strong&gt; and add your email provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"smtp.gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth_protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-email@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-app-password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hello_hostname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tls_enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tls_skip_verify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_conns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"idle_timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wait_timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_msg_retries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email_headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Create Your First List
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Lists&lt;/strong&gt; and create your first mailing list&lt;/li&gt;
&lt;li&gt;Set up subscription preferences&lt;/li&gt;
&lt;li&gt;Configure double opt-in if needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ug98lvydd2cj408xc12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ug98lvydd2cj408xc12.png" alt="create-campaigns" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Design Your Templates
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Campaigns &amp;gt; Templates&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Customize the default template or create your own&lt;/li&gt;
&lt;li&gt;Use Listmonk's template variables for personalization&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features to Explore
&lt;/h2&gt;

&lt;p&gt;Once you're set up, here are the powerful features you can leverage:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Campaign Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rich text editor with template support&lt;/li&gt;
&lt;li&gt;A/B testing capabilities&lt;/li&gt;
&lt;li&gt;Scheduled sending&lt;/li&gt;
&lt;li&gt;Campaign analytics and tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Subscriber Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Import/export subscribers&lt;/li&gt;
&lt;li&gt;Custom attributes and segmentation&lt;/li&gt;
&lt;li&gt;Subscription preferences&lt;/li&gt;
&lt;li&gt;Bounce and complaint handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;API Integration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://listmonk.app/docs/apis/apis/" rel="noopener noreferrer"&gt;RESTful API&lt;/a&gt; for all operations&lt;/li&gt;
&lt;li&gt;Webhook support&lt;/li&gt;
&lt;li&gt;Easy integration with your applications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production Considerations
&lt;/h2&gt;

&lt;p&gt;For a production deployment, consider these improvements:&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Security
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Change default credentials&lt;/span&gt;
&lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-custom-user&lt;/span&gt;
&lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-secure-db-password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backup Strategy
&lt;/h3&gt;

&lt;p&gt;Set up automated backups for your PostgreSQL data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example backup script&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;listmonk_db pg_dump &lt;span class="nt"&gt;-U&lt;/span&gt; listmonk listmonk &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; backup-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resource Limits
&lt;/h3&gt;

&lt;p&gt;Add resource constraints to your containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
      &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cost Comparison
&lt;/h2&gt;

&lt;p&gt;Let's look at the numbers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mailchimp&lt;/strong&gt; (10,000 subscribers): $99/month = $1,188/year&lt;br&gt;
&lt;strong&gt;ConvertKit&lt;/strong&gt; (10,000 subscribers): $119/month = $1,428/year&lt;br&gt;
&lt;strong&gt;Listmonk Self-hosted&lt;/strong&gt;: VPS ($20/month) = $240/year&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual savings&lt;/strong&gt;: $948-$1,188 with complete data ownership!&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Container won't start&lt;/strong&gt;: Check your environment variables and ensure the database is healthy first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can't send emails&lt;/strong&gt;: Verify your SMTP settings and check if your provider requires app passwords (Gmail) or API keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance issues&lt;/strong&gt;: Monitor your database resources and consider upgrading your server if you have a large subscriber base.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Self-hosting Listmonk with Coolify gives you enterprise-grade email marketing capabilities without the recurring costs. You maintain complete control over your data while enjoying a modern, feature-rich platform that scales with your needs.&lt;/p&gt;

&lt;p&gt;The initial setup might take 30 minutes, but the long-term benefits in cost savings, privacy, and control make it worthwhile for any serious content creator or business.&lt;/p&gt;

&lt;p&gt;Ready to take control of your email marketing? Give Listmonk a try – your subscribers (and your wallet) will thank you!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you set up Listmonk with Coolify? Share your experience in the comments below! And if you found this guide helpful, don't forget to ❤️ and follow for more self-hosting tutorials.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>newsletter</category>
      <category>selfhosting</category>
      <category>opensource</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Self-Hosted Analytics with Rybbit: A Game-Changer for Privacy-Conscious Developers</title>
      <dc:creator>Jaskaran Deogan</dc:creator>
      <pubDate>Sat, 06 Sep 2025 06:39:29 +0000</pubDate>
      <link>https://dev.to/jaskarandeogan/self-hosted-analytics-with-rybbit-a-game-changer-for-privacy-conscious-developers-34ff</link>
      <guid>https://dev.to/jaskarandeogan/self-hosted-analytics-with-rybbit-a-game-changer-for-privacy-conscious-developers-34ff</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60g4xpeyqbs16ryq6x73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60g4xpeyqbs16ryq6x73.png" alt="Rybbit Dashboard" width="800" height="567"&gt;&lt;/a&gt;&lt;br&gt;
As AI adoption accelerates day by day, so does the number of people creating web applications. With tools like Loveable and Vercel's V0, it's never been easier to test your ideas and build MVPs that capture the essence of your vision. However, once you transition from prototype to production and start onboarding real users, understanding their behavior becomes crucial.&lt;/p&gt;

&lt;p&gt;You need insights into user experience, system performance, and potential errors. You need analytics that tell you everything about the traffic flowing through your website. But here's the thing – &lt;strong&gt;can you really trust third-party services with your precious user data?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Analytics Dilemma: Privacy vs. Convenience
&lt;/h2&gt;

&lt;p&gt;Sure, Google Analytics exists. It's free, feature-rich, and widely adopted. But as more developers embrace the "vibecoding" philosophy and rapid prototyping becomes the norm, trusting third parties with sensitive user data becomes increasingly questionable.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;self-sovereign analytics&lt;/strong&gt; – the idea that you should own and control your analytics data completely. This is where &lt;a href="https://www.rybbit.io/" rel="noopener noreferrer"&gt;Rybbit&lt;/a&gt; absolutely shines, and honestly, I'm blown away by its open-source capabilities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4712eiuhpzyp4q6uqsso.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4712eiuhpzyp4q6uqsso.png" alt="Realtime Analytics" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3b7djvpubbhy7b2jas5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3b7djvpubbhy7b2jas5o.png" alt="Analytic Events" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Rybbit Caught My Attention
&lt;/h2&gt;

&lt;p&gt;Rybbit isn't just another analytics tool – it's a complete, self-hosted analytics platform that gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔒 &lt;strong&gt;Complete data ownership&lt;/strong&gt; - Your data stays on your infrastructure&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Real-time insights&lt;/strong&gt; - Live traffic monitoring and user behavior tracking
&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Performance monitoring&lt;/strong&gt; - Track errors, page load times, and system health&lt;/li&gt;
&lt;li&gt;🛡️ &lt;strong&gt;Privacy-first approach&lt;/strong&gt; - No third-party data sharing or tracking&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;Enhanced Analytics on user testing&lt;/strong&gt; - Additional tools are available to test the app’s functionality, including enhanced error analytics and user session replays.&lt;/li&gt;
&lt;li&gt;📈 &lt;strong&gt;Beautiful dashboards&lt;/strong&gt; - Intuitive interface that rivals commercial solutions&lt;/li&gt;
&lt;li&gt;🔧 &lt;strong&gt;Full customization&lt;/strong&gt; - Modify and extend as needed since it's open source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rybbit &lt;a href="https://github.com/rybbit-io/rybbit" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploying Rybbit on Coolify: The Complete Guide
&lt;/h2&gt;

&lt;p&gt;If you want to simply run Rybbit on your VPS, the official &lt;a href="https://www.rybbit.io/docs/self-hosting" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; has you covered. But if you're like me and want to deploy it on &lt;strong&gt;Coolify with proper health checks and production-ready configuration&lt;/strong&gt;, this guide is for you.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Architecture
&lt;/h3&gt;

&lt;p&gt;Rybbit is a multi-service application that requires several components working in harmony:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js client (port 3002) &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Fastify API server (port 3001)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: PostgreSQL for user data and configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics DB&lt;/strong&gt;: ClickHouse for high-performance analytics storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache&lt;/strong&gt;: Redis for session management and caching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reverse Proxy&lt;/strong&gt;: Handled automatically by Coolify&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 1: Setting Up the Docker Compose Configuration
&lt;/h3&gt;

&lt;p&gt;The magic happens in the &lt;code&gt;coolify-compose.yml&lt;/code&gt; file. Here's the production-ready configuration with health checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clickhouse&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clickhouse/clickhouse-server:25.4.2&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;clickhouse-data:/var/lib/clickhouse&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CLICKHOUSE_DB=${CLICKHOUSE_DB:-analytics}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CLICKHOUSE_USER=${CLICKHOUSE_USER:-default}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-frog}&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wget"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--no-verbose"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--tries=1"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--spider"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8123/ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:17.4&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=${POSTGRES_USER:-frog}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-frog}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=${POSTGRES_DB:-analytics}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres-data:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$${POSTGRES_USER}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$${POSTGRES_DB}"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis-data:/data&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--requirepass"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${REDIS_PASSWORD:-changeme}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--appendonly"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--raw"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incr"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;

  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./server/Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CLICKHOUSE_HOST=http://clickhouse:8123&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_HOST=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_HOST=redis&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BASE_URL=${BASE_URL}&lt;/span&gt;
      &lt;span class="c1"&gt;# ... other environment variables&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;clickhouse&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./client/Dockerfile&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${NEXT_PUBLIC_BACKEND_URL}&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NEXT_PUBLIC_BACKEND_URL=${NEXT_PUBLIC_BACKEND_URL}&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clickhouse-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Environment Variables Configuration
&lt;/h3&gt;

&lt;p&gt;Set these in your Coolify application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Required&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;analytics.yourdomain.com
&lt;span class="nv"&gt;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://analytics.yourdomain.com
&lt;span class="nv"&gt;BETTER_AUTH_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-super-secure-secret-here
&lt;span class="nv"&gt;NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.analytics.yourdomain.com

&lt;span class="c"&gt;# Database credentials&lt;/span&gt;
&lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;frog
&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-secure-postgres-password
&lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;analytics
&lt;span class="nv"&gt;CLICKHOUSE_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;analytics
&lt;span class="nv"&gt;CLICKHOUSE_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-secure-clickhouse-password
&lt;span class="nv"&gt;REDIS_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-secure-redis-password

&lt;span class="c"&gt;# Optional&lt;/span&gt;
&lt;span class="nv"&gt;DISABLE_SIGNUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false
&lt;/span&gt;&lt;span class="nv"&gt;DISABLE_TELEMETRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Deploy in Coolify
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a new &lt;strong&gt;Application&lt;/strong&gt; in Coolify&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Docker Compose&lt;/strong&gt; as the deployment method&lt;/li&gt;
&lt;li&gt;Connect your Git repository&lt;/li&gt;
&lt;li&gt;Set the compose file to &lt;code&gt;coolify-compose.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Configure all environment variables&lt;/li&gt;
&lt;li&gt;Deploy!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Common Issues and Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. API Proxy Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The most common issue&lt;/strong&gt;: Frontend can't communicate with the backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Ensure your Next.js configuration properly handles API proxying. The frontend should proxy &lt;code&gt;/api/*&lt;/code&gt; calls to the backend service. Make sure &lt;code&gt;NEXT_PUBLIC_BACKEND_URL&lt;/code&gt; points to your domain, not the internal backend service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// client/next.config.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;rewrites&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/:path*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://backend:3001/api/:path*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Health Check Failures
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;: Services failing health checks and restarting continuously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Verify that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database credentials match across all services&lt;/li&gt;
&lt;li&gt;Network connectivity between containers is working&lt;/li&gt;
&lt;li&gt;Health check endpoints are responding correctly
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test backend health&lt;/span&gt;
curl &lt;span class="nt"&gt;-f&lt;/span&gt; http://localhost:3001/api/health

&lt;span class="c"&gt;# Test database connectivity&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; postgres-container pg_isready &lt;span class="nt"&gt;-U&lt;/span&gt; frog &lt;span class="nt"&gt;-d&lt;/span&gt; analytics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Build Context Issues
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;: Docker builds failing due to incorrect context or Dockerfile paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Ensure your build context is set to the repository root, and Dockerfile paths are relative to that context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;  &lt;span class="c1"&gt;# Repository root&lt;/span&gt;
    &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./server/Dockerfile&lt;/span&gt;  &lt;span class="c1"&gt;# Relative to context&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Benefits of This Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔄 Graceful Service Dependencies
&lt;/h3&gt;

&lt;p&gt;Services start in the correct order with health condition checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Databases (PostgreSQL, ClickHouse, Redis) initialize first&lt;/li&gt;
&lt;li&gt;Backend waits for all databases to be healthy&lt;/li&gt;
&lt;li&gt;Frontend waits for backend to be healthy&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  🏥 Comprehensive Health Monitoring
&lt;/h3&gt;

&lt;p&gt;Every service includes health checks that Coolify can monitor and act upon automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Production-Ready Optimizations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Standalone Next.js builds for containerized deployment&lt;/li&gt;
&lt;li&gt;Conditional compression loading (zstd with gzip fallback)&lt;/li&gt;
&lt;li&gt;Proper restart policies and resource management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;In an era where data privacy is paramount and vendor lock-in is a real concern, self-hosted analytics solutions like Rybbit represent the future. You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complete control&lt;/strong&gt; over your analytics data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No monthly fees&lt;/strong&gt; or usage limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customization freedom&lt;/strong&gt; to add features you need&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance confidence&lt;/strong&gt; knowing where your data lives&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance benefits&lt;/strong&gt; from local data processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.rybbit.io/" rel="noopener noreferrer"&gt;Rybbit&lt;/a&gt; on Coolify is a winning combination for developers who value privacy, performance, and control. The setup might seem complex initially, but the payoff is enormous – you get enterprise-grade analytics without the enterprise price tag or privacy concerns.&lt;/p&gt;

&lt;p&gt;The open-source nature means you can contribute back to the community, customize it for your specific needs, and never worry about a service shutting down or changing their pricing model.&lt;/p&gt;

&lt;p&gt;Have you tried self-hosted analytics? What's been your experience with tools like Rybbit? I'd love to hear your thoughts in the comments!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rybbit-io/rybbit" rel="noopener noreferrer"&gt;Rybbit GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/jaskarandeogan/how-to-self-host-coolify-on-your-vps-a-complete-guide-4jfh"&gt;Coolify Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose Reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Happy analytics tracking! 📊&lt;/em&gt;&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>vibecoding</category>
      <category>privacy</category>
      <category>developers</category>
    </item>
    <item>
      <title>How to Self-Host Coolify on Your VPS: A Complete Guide</title>
      <dc:creator>Jaskaran Deogan</dc:creator>
      <pubDate>Mon, 12 May 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jaskarandeogan/how-to-self-host-coolify-on-your-vps-a-complete-guide-4jfh</link>
      <guid>https://dev.to/jaskarandeogan/how-to-self-host-coolify-on-your-vps-a-complete-guide-4jfh</guid>
      <description>&lt;p&gt;Deploying applications can be a complex process, especially if you're managing multiple projects. Coolify offers a streamlined solution for developers who want control without the complexity. In this guide, I'll walk you through what Coolify is and how to set it up on your own VPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Coolify?
&lt;/h2&gt;

&lt;p&gt;Coolify is an open-source, self-hostable alternative to platforms like Heroku and Netlify. It's designed to simplify the deployment and management of applications, websites, and databases on your own infrastructure. With Coolify, you can deploy applications directly from Git repositories with just a few clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Coolify?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Complete Control: You own your infrastructure and data&lt;/li&gt;
&lt;li&gt;Cost-Effective: Eliminate ongoing SaaS subscription fees&lt;/li&gt;
&lt;li&gt;Privacy: Your code and data stay on your servers&lt;/li&gt;
&lt;li&gt;Customization: Configure everything to your exact needs&lt;/li&gt;
&lt;li&gt;One-Click Deployments: Deploy applications straight from your Git repositories&lt;/li&gt;
&lt;li&gt;Multiple Application Support: Host various applications, websites, and databases in one place&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before installing Coolify, ensure your VPS meets these requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At least 2 CPU cores&lt;/li&gt;
&lt;li&gt;Minimum 4 GB RAM&lt;/li&gt;
&lt;li&gt;40 GB storage&lt;/li&gt;
&lt;li&gt;Ubuntu 20.04 or newer (Ubuntu 24.04 recommended)&lt;/li&gt;
&lt;li&gt;Root or sudo access&lt;/li&gt;
&lt;li&gt;SSH access&lt;/li&gt;
&lt;li&gt;Internet connection&lt;/li&gt;
&lt;li&gt;Basic understanding of Docker&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step-by-Step Installation Guide
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Step 1: Connect to Your VPS
&lt;/h4&gt;

&lt;p&gt;Connect to your server via SSH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh root@your_server_ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Update Your System
&lt;/h4&gt;

&lt;p&gt;Always start with a fully updated system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt update &amp;amp;&amp;amp; apt upgrade -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Verify Docker Installation
&lt;/h4&gt;

&lt;p&gt;Coolify requires Docker and Docker Compose. Check if Docker is already installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Docker isn't installed, follow the official Docker installation guide for your Ubuntu version from the Docker documentation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 4: Install Coolify
&lt;/h4&gt;

&lt;p&gt;The simplest way to install Coolify is using the official installation script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 5: Access Coolify Dashboard
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuf14eke6bzd9q4mk9elm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuf14eke6bzd9q4mk9elm.png" alt="successful installation" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After installation completes successfully, the script will display your Coolify URL (typically &lt;code&gt;http://your_server_ip:8000&lt;/code&gt;).&lt;br&gt;
To access your dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit this URL in your web browser&lt;/li&gt;
&lt;li&gt;You'll be redirected to a registration page&lt;/li&gt;
&lt;li&gt;Create your first admin account immediately&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Making Coolify Publicly Accessible
&lt;/h3&gt;

&lt;p&gt;By default, Coolify might only be accessible locally. Let's configure it for public access.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 1: Configure Firewall Rules
&lt;/h4&gt;

&lt;p&gt;First, check your firewall status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If UFW isn't installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install ufw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Allow SSH
sudo ufw allow ssh

# Allow Coolify main port
sudo ufw allow 8000/tcp

# Allow HTTP and HTTPS for deployed applications
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable the firewall
sudo ufw enable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Verify Port Binding
&lt;/h4&gt;

&lt;p&gt;Ensure Coolify is listening on all interfaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo netstat -tulpn | grep 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see it bound to 0.0.0.0:8000 rather than just 127.0.0.1:8000.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Restart Coolify
&lt;/h4&gt;

&lt;p&gt;After making changes, restart Coolify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /data/coolify/source
docker compose down
docker compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Test External Access
&lt;/h4&gt;

&lt;p&gt;Now try accessing Coolify from outside your server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://your-server-ip:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09gta3omguj79ie5xbrg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09gta3omguj79ie5xbrg.png" alt="Coolify Dashboard" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don't know your server's public IP address, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -4 icanhazip.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting Up a Custom Domain for Your Coolify Dashboard
&lt;/h3&gt;

&lt;p&gt;For a more professional setup, you can configure Coolify to use your own domain.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Add DNS Record
&lt;/h4&gt;

&lt;p&gt;First, add an A record at your domain registrar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type: A&lt;/li&gt;
&lt;li&gt;Name: coolify (or subdomain of your choice, or @ for root domain)&lt;/li&gt;
&lt;li&gt;Value: Your server's IP address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 2: Update Coolify Configuration&lt;br&gt;
Modify Coolify's environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /data/coolify/source

# Backup existing environment file
cp .env .env.backup

# Edit the .env file
nano .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add or update these variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_URL=https://yourdomain.com
COOLIFY_FQDN=yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: Restart Coolify with New Configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /data/coolify/source

# Stop everything
docker compose --env-file .env -f docker-compose.yml -f docker-compose.prod.yml stop

# Start everything fresh
docker compose --env-file .env -f docker-compose.yml -f docker-compose.prod.yml up -d

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Coolify should now be accessible via your custom domain!&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying Your Installation
&lt;/h3&gt;

&lt;p&gt;To ensure everything is running properly:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Check Container Status:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for Coolify-related containers running without errors.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Check Logs:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /data/coolify/source
docker compose logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Test Dashboard Access:
&lt;/h4&gt;

&lt;p&gt;Visit your dashboard URL (either IP:8000 or your custom domain) in a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Coolify provides a powerful, self-hosted platform for managing your applications. By following this guide, you've successfully set up your own deployment platform that gives you complete control over your infrastructure.&lt;/p&gt;

&lt;p&gt;With Coolify up and running, you can now deploy websites, applications, and databases with ease—all from a clean, user-friendly dashboard that you control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming
&lt;/h2&gt;

&lt;p&gt;explore more open-source technologies while solving real life problems.&lt;/p&gt;

&lt;p&gt;Stay tuned and happy deploying!&lt;/p&gt;

&lt;p&gt;Official documentation &lt;a href="https://coolify.io/docs/get-started/installation" rel="noopener noreferrer"&gt;Coolify&lt;/a&gt;&lt;/p&gt;

</description>
      <category>coolify</category>
      <category>docker</category>
      <category>learning</category>
      <category>community</category>
    </item>
    <item>
      <title>How I Slashed Infrastructure Costs by 80% for a Struggling Startup</title>
      <dc:creator>Jaskaran Deogan</dc:creator>
      <pubDate>Thu, 08 May 2025 20:18:15 +0000</pubDate>
      <link>https://dev.to/jaskarandeogan/how-i-slashed-infrastructure-costs-by-80-for-a-struggling-startup-23</link>
      <guid>https://dev.to/jaskarandeogan/how-i-slashed-infrastructure-costs-by-80-for-a-struggling-startup-23</guid>
      <description>&lt;p&gt;When I took over a project for a client recently, I discovered they were spending nearly $1,000 monthly on AWS infrastructure despite having fewer than 5 active users. This is how I fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;br&gt;
I inherited: messy React frontend with 1000+ line components&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Poorly designed Flask backend&lt;/li&gt;
&lt;li&gt;Everything running in development mode on expensive AWS EC2 instances&lt;/li&gt;
&lt;li&gt;A client who had just laid off their team and needed an MVP launched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;&lt;br&gt;
Instead of optimizing the existing AWS setup, I took a completely different approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Purchased two high-performance dedicated servers from Hetzner&lt;/li&gt;
&lt;li&gt;Self-hosted Coolify as a Platform-as-a-Service solution&lt;/li&gt;
&lt;li&gt;Containerized all application components&lt;/li&gt;
&lt;li&gt;Implemented CICD with Github for streamlined deployments.&lt;/li&gt;
&lt;li&gt;Migrated everything away from AWS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those unfamiliar, Coolify is an open-source alternative to Netlify or Heroku that can be self-hosted on your own infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Results&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;80% reduction in monthly infrastructure costs (from ~$1,000 to ~$&amp;lt;200)&lt;/li&gt;
&lt;li&gt;More stable environment with proper containerization&lt;/li&gt;
&lt;li&gt;Simplified deployment process&lt;/li&gt;
&lt;li&gt;Full control without vendor lock-in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway&lt;/strong&gt;&lt;br&gt;
Infrastructure decisions made early can dramatically impact your startup's runway. In this case, the original setup was burning cash that could have been better spent on product development or marketing.&lt;/p&gt;

&lt;p&gt;Before automatically choosing the most popular cloud provider, consider alternatives like Hetzner combined with self-hosted PaaS solutions like Coolify. For many early-stage startups, this approach provides the right balance of developer experience and cost-efficiency.&lt;/p&gt;

&lt;p&gt;Sometimes the best technical solution isn't the most expensive one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upcoming&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Self-host Coolify on your own infrastructure&lt;/li&gt;
&lt;li&gt;Host your own services like analytics, project management tools, interactive forms like Formspree, and much more. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>startup</category>
      <category>infrastructureascode</category>
      <category>aws</category>
    </item>
    <item>
      <title>Docker Advance: Mastering Containerization</title>
      <dc:creator>Jaskaran Deogan</dc:creator>
      <pubDate>Thu, 08 May 2025 07:00:00 +0000</pubDate>
      <link>https://dev.to/jaskarandeogan/docker-advance-mastering-containerization-59bm</link>
      <guid>https://dev.to/jaskarandeogan/docker-advance-mastering-containerization-59bm</guid>
      <description>&lt;p&gt;As an experienced developer, you're beyond the basics of &lt;code&gt;docker run&lt;/code&gt; and &lt;code&gt;docker build&lt;/code&gt;. You need to orchestrate complex, production-ready environments with precision. This guide dives deep into creating robust Docker deployments through YAML configurations, advanced networking, and container health management.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Power of Docker Compose YAML
&lt;/h2&gt;

&lt;p&gt;Docker Compose YAML files are the cornerstone of sophisticated multi-container applications. They provide a declarative way to define your entire infrastructure stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomy of a Production Docker Compose File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.production&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUILD_ENV=production&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${REGISTRY_URL}/myapp/api:${TAG:-latest}&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.50'&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
      &lt;span class="na"&gt;restart_policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on-failure&lt;/span&gt;
        &lt;span class="na"&gt;max_attempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;40s&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://user:pass@db:5432/app&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api_key&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14-alpine&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db-data:/var/lib/postgresql/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./init-scripts:/docker-entrypoint-initdb.d&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD_FILE=/run/secrets/db_password&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db_password&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:alpine&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--appendonly"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis-data:/data&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;

  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/nginx.conf:/etc/nginx/nginx.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/conf:/etc/letsencrypt&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/www:/var/www/certbot&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;driver_opts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none'&lt;/span&gt;
      &lt;span class="na"&gt;o&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bind'&lt;/span&gt;
      &lt;span class="na"&gt;device&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/mnt/data/postgres'&lt;/span&gt;
  &lt;span class="na"&gt;redis-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;ipam&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;subnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.16.238.0/24&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;ipam&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;subnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.16.239.0/24&lt;/span&gt;

&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./secrets/api_key.txt&lt;/span&gt;
  &lt;span class="na"&gt;db_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./secrets/db_password.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Configuration Components
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Service Configuration
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build Context and Arguments&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
     &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.production&lt;/span&gt;
     &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUILD_ENV=production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separates your build context from your Dockerfile location, allowing for complex build setups and parameterized builds.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Resource Limits&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.50'&lt;/span&gt;
         &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting hard limits prevents container resource contention and protects your host system.&lt;/p&gt;

&lt;h4&gt;
  
  
  Health Checks - The Reliability Guardian
&lt;/h4&gt;

&lt;p&gt;Health checks are critical for production readiness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
  &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;40s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polls your service's health endpoint every 30 seconds&lt;/li&gt;
&lt;li&gt;Allows 10 seconds for a response&lt;/li&gt;
&lt;li&gt;Retries 3 times before marking unhealthy&lt;/li&gt;
&lt;li&gt;Provides a 40-second grace period during startup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Health checks enable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Self-healing containers that restart when services fail&lt;/li&gt;
&lt;li&gt;Dependency ordering based on actual service readiness&lt;/li&gt;
&lt;li&gt;Orchestration-level service discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Networking - Isolated and Secure
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;ipam&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;subnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.16.238.0/24&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;ipam&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;subnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.16.239.0/24&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;internal: true&lt;/code&gt; flag for the backend network is crucial - it prevents any container in this network from establishing outbound connections to the internet, creating a security boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Docker Image Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Multi-stage Builds for Production Efficiency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Production stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV production&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/package.json ./&lt;/span&gt;

&lt;span class="c"&gt;# Use non-root user for security&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-g&lt;/span&gt; 1001 &lt;span class="nt"&gt;-S&lt;/span&gt; nodejs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;-S&lt;/span&gt; nodejs &lt;span class="nt"&gt;-u&lt;/span&gt; 1001 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nodejs:nodejs /app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nodejs&lt;/span&gt;

&lt;span class="c"&gt;# Health check&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=10s --start-period=40s --retries=3 \&lt;/span&gt;
    CMD wget -q -O - http://localhost:3000/health || exit 1

&lt;span class="c"&gt;# Runtime configuration&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/main.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Importance of Base Image Selection
&lt;/h3&gt;

&lt;p&gt;Base image selection dramatically impacts security, size, and performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Distroless Images&lt;/strong&gt;: Contain only your application and its runtime dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpine Variants&lt;/strong&gt;: Minimal footprint but include package managers for flexibility&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scratch Images&lt;/strong&gt;: Empty images for compiled languages, offering the smallest attack surface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare the image size differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node on Debian: ~900MB&lt;/li&gt;
&lt;li&gt;Node on Alpine: ~170MB&lt;/li&gt;
&lt;li&gt;Go compiled binary on scratch: ~10MB&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Volume Management - Data Persistence Done Right
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;driver_opts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none'&lt;/span&gt;
      &lt;span class="na"&gt;o&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bind'&lt;/span&gt;
      &lt;span class="na"&gt;device&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/mnt/data/postgres'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration maps container data to specific host locations with precise mounting options, ensuring data integrity across container lifecycles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets Management - The Secure Way
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./secrets/api_key.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secrets in Docker Compose are mounted as files, not environment variables, preventing credential leakage in process lists or error logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Design Principles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Creating Defense-in-Depth
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Least Privilege Networking&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend-facing services in one network&lt;/li&gt;
&lt;li&gt;Backend services in an internal network&lt;/li&gt;
&lt;li&gt;Database in its own isolated network&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network Segmentation Benefits&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limits attack vectors if perimeter is breached&lt;/li&gt;
&lt;li&gt;Provides clear traffic flow visibility&lt;/li&gt;
&lt;li&gt;Enables granular firewall rules&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Custom Bridge Networks with Static IPs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;ipam&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;subnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.28.0.0/16&lt;/span&gt;
          &lt;span class="na"&gt;ip_range&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.28.5.0/24&lt;/span&gt;
          &lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.28.5.254&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Static IP assignments allow for deterministic network configurations, especially useful when integrating with legacy systems or configuring static firewall rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Container Dependency Management
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures your application only starts when its dependencies are truly ready to accept connections, not just when their containers have started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Advanced Approach Matters
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code&lt;/strong&gt;: Your entire environment is versioned, reproducible, and testable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency Across Environments&lt;/strong&gt;: Dev, staging, and production use identical configurations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security by Design&lt;/strong&gt;: Network isolation, minimal base images, and proper secret management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Disaster Recovery&lt;/strong&gt;: Rebuild your entire stack with a single command&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient Resource Utilization&lt;/strong&gt;: Precise resource limits prevent waste and contention&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Moving Beyond Basic Docker
&lt;/h2&gt;

&lt;p&gt;When working at scale, consider these advanced patterns:&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration Management with Docker Configs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nginx_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./nginx.conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike volumes, configs are immutable and better represent configuration as code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Health Check Implementations
&lt;/h3&gt;

&lt;p&gt;For complex applications, implement sophisticated health checks that verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connections&lt;/li&gt;
&lt;li&gt;External API availability&lt;/li&gt;
&lt;li&gt;Cache system functionality&lt;/li&gt;
&lt;li&gt;Queue processor status&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Init Systems and Zombie Process Prevention
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;init: true&lt;/code&gt; flag adds a lightweight init system to your container, preventing zombie processes and ensuring proper signal handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Path to Orchestration
&lt;/h2&gt;

&lt;p&gt;This advanced Docker Compose setup forms the foundation for moving to orchestration platforms like Kubernetes. The principles remain the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declarative configuration&lt;/li&gt;
&lt;li&gt;Health monitoring&lt;/li&gt;
&lt;li&gt;Resource management&lt;/li&gt;
&lt;li&gt;Network segmentation&lt;/li&gt;
&lt;li&gt;Secret handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By mastering these Docker concepts, you're not just containerizing applications—you're building production-ready, scalable, and secure systems that embody DevOps best practices.&lt;/p&gt;

&lt;p&gt;The true power of Docker isn't in simplifying development; it's in enabling consistent, repeatable infrastructure that bridges the gap between development and operations, turning the art of deployment into a precise science.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to self-host Coolify - an open source alternative to Netlify or Heroku. &lt;/li&gt;
&lt;li&gt;self-host services like analytics, email-server, interactive-forms, databases and much more!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>webdev</category>
      <category>cloud</category>
      <category>cloudskills</category>
    </item>
    <item>
      <title>What is Docker and Why Should You Care?</title>
      <dc:creator>Jaskaran Deogan</dc:creator>
      <pubDate>Tue, 06 May 2025 20:39:39 +0000</pubDate>
      <link>https://dev.to/jaskarandeogan/what-is-docker-and-why-should-you-care-2608</link>
      <guid>https://dev.to/jaskarandeogan/what-is-docker-and-why-should-you-care-2608</guid>
      <description>&lt;p&gt;Look, I get it. When I started out 3 years ago, I was all about getting my apps online the quickest way possible. Vercel for my Next.js projects, Netlify for static sites, and PM2 to keep my Node.js apps running on that cheap VPS I rented. It worked... until it didn't.&lt;/p&gt;

&lt;p&gt;If you're like me, I highly encourage you to explore about Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deployment Methods I Used to Swear By
&lt;/h2&gt;

&lt;p&gt;I thought I had it all figured out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The One-Click Wonder&lt;/strong&gt;: Push to GitHub, automatic deployment to a hosting platform. Magic! Until I needed custom environment configurations or hit the free tier limits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The PM2 Savior&lt;/strong&gt;: SSH into my server, git pull, npm install, pm2 restart. Simple enough... except when dependencies broke, or node versions mismatched, or any of the other thousand things that could go wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The "It's Working, Don't Touch It" Server&lt;/strong&gt;: That Ubuntu VPS running four different apps with a tangled mess of nginx configurations. Heaven forbid I needed to update anything!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why It All Started Breaking Down
&lt;/h2&gt;

&lt;p&gt;As my projects grew, the cracks started showing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Hell&lt;/strong&gt;: "Works on my machine" became my catchphrase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration Nightmares&lt;/strong&gt;: Each service had its own way of setting environment variables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget Stress&lt;/strong&gt;: Free tiers ran out fast, and suddenly I was paying $50+ monthly for basic hosting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling Issues&lt;/strong&gt;: Adding new features meant fighting with deployment configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popular Blunders&lt;/strong&gt;:  I was once told by someone in the industry how their aws bill went up to 100k USD while their product was still in development. I also experienced myself with AWS where their elastic compute is just out of control. Someone new to this tech is bound to make mistakes which can result in huge loss for the company or you. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Docker: The Game-Changer
&lt;/h2&gt;

&lt;p&gt;It's so powerful. You can run multiple containers on the same server/device just like that. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Environment Consistency
&lt;/h3&gt;

&lt;p&gt;No more "but it works on my machine" moments. Docker ensures my app runs the same way everywhere. Everything my app needs is defined in a Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Portable Development
&lt;/h3&gt;

&lt;p&gt;I can now share my entire development setup with a teammate by sending them a single file. No more "First install Node 16, then PostgreSQL 13, then configure this environment variable..."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Local Testing That Actually Matches Production
&lt;/h3&gt;

&lt;p&gt;With Docker, I test in an environment identical to production. No more "it worked in development" problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Freedom From Provider Lock-in
&lt;/h3&gt;

&lt;p&gt;Before, switching hosting providers meant learning a whole new deployment process. Now, I can take my containerized app anywhere that runs Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  System Requirements - What You Need
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For Local Development:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Minimum Requirements&lt;/th&gt;
&lt;th&gt;Recommended&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;2 cores&lt;/td&gt;
&lt;td&gt;4+ cores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;4GB&lt;/td&gt;
&lt;td&gt;8GB+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk Space&lt;/td&gt;
&lt;td&gt;10GB free&lt;/td&gt;
&lt;td&gt;20GB+ free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Windows 10+, macOS 10.15+, Linux with kernel 3.10+&lt;/td&gt;
&lt;td&gt;Latest versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Installation&lt;/td&gt;
&lt;td&gt;Docker Desktop or Docker Engine&lt;/td&gt;
&lt;td&gt;Latest stable version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  For Cloud Deployment:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Minimum&lt;/th&gt;
&lt;th&gt;Recommended&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;1 shared core&lt;/td&gt;
&lt;td&gt;2+ dedicated cores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;512MB&lt;/td&gt;
&lt;td&gt;2GB+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;5GB&lt;/td&gt;
&lt;td&gt;20GB+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bandwidth&lt;/td&gt;
&lt;td&gt;1GB/month&lt;/td&gt;
&lt;td&gt;100GB+/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;Docker Engine 20.10+&lt;/td&gt;
&lt;td&gt;Latest version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How I Migrated My Next.js App to Docker
&lt;/h2&gt;

&lt;p&gt;Switching was surprisingly easy:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Add a .dockerignore File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.next
.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Build and Run My Container
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build the Docker image&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-nextjs-app &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Run the container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 my-nextjs-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like that, my app was running in a consistent, portable environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Saved Money
&lt;/h2&gt;

&lt;p&gt;The biggest revelation was realizing I could replace multiple paid services with self-hosted alternatives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database Costs&lt;/strong&gt;: Instead of paying for a managed database, I run PostgreSQL in a container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Projects&lt;/strong&gt;: I can host several Docker containers on a single affordable VPS instead of paying per-project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form Handling&lt;/strong&gt;: No need for form submission services when I can run a simple form-handling self-hosted service in a container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics&lt;/strong&gt;: Replaced paid analytics with a self-hosted open-source solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The "I Wish Someone Told Me This Sooner" Moment
&lt;/h2&gt;

&lt;p&gt;Looking back, I wasted so much time fighting with deployment issues that Docker would have solved instantly. If you're still manually SSHing into servers or hitting the limits of one-click deploys, do yourself a favor and spend a day learning Docker.&lt;/p&gt;

&lt;p&gt;The knowledge transfers everywhere, works for any programming language or framework, and will save you countless hours of debugging environment-specific issues.&lt;/p&gt;

&lt;p&gt;Docker isn't just another tool in your stack—it's a completely different approach to development and deployment that makes everything else easier.&lt;/p&gt;

&lt;p&gt;Are you still using PM2 and praying your server doesn't crash? Still hitting free tier limits on deployment platforms? It might be time to containerize your way to developer happiness.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>selfhosting</category>
      <category>cloudcomputing</category>
      <category>aws</category>
    </item>
    <item>
      <title>Supercharge Your UI Development with Storybook</title>
      <dc:creator>Jaskaran Deogan</dc:creator>
      <pubDate>Sun, 19 Mar 2023 20:06:57 +0000</pubDate>
      <link>https://dev.to/jaskarandeogan/supercharge-your-ui-development-with-storybook-1nhb</link>
      <guid>https://dev.to/jaskarandeogan/supercharge-your-ui-development-with-storybook-1nhb</guid>
      <description>&lt;p&gt;"Storybook for Component-Driven Development: A Comprehensive Guide" - Learn how Storybook simplifies UI component development by allowing developers to view and interact with components in isolation. Discover the benefits of using Storybook to document component-driven development and get started with it today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Storybook is Useful for Documenting Component-Driven Development&lt;/strong&gt;&lt;br&gt;
Storybook is valuable for documenting components in a clear and accessible way. It's especially helpful for large-scale projects with multiple developers. Teams can create their own stories to review each other's work and catch errors early on. It also helps improve code quality by isolating and testing components in various states. This reduces debugging time and enhances the overall stability of the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Get Started with Storybook&lt;/strong&gt;&lt;br&gt;
Getting started with Storybook is relatively easy. Here are the basic steps:&lt;/p&gt;

&lt;p&gt;Install Storybook using your preferred package manager. For example, you can use the following command with npm:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install -D @storybook/react&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a configuration file for Storybook. You can use the following command to create a basic configuration file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx sb init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Note: depending upon your styling, you may have to configure this further. check official &lt;a href="https://storybook.js.org/docs/react/get-started/why-storybook" rel="noopener noreferrer"&gt;documentation &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create your first story. You can create a new file in your project's src directory. Here is a small demo how I created a story of one of my component.&lt;/p&gt;

&lt;p&gt;src/components/ProgressBar/ProgressBar.stories.js. Here's an example of what the file might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";
import { Meta, Story } from "@storybook/react";
import CircularProgressBar from "../components/CircularProgressBar";
import { CircularProgressBarProps } from "@/types/components";

export default {
  title: "Components/CircularProgressBar",
  component: CircularProgressBar,
  layout: "fullscreen",
} as Meta;

const Template: Story&amp;lt;CircularProgressBarProps&amp;gt; = (args) =&amp;gt; (
  &amp;lt;CircularProgressBar {...args} /&amp;gt;
);

export const Default = Template.bind({});
Default.args = {
  children: (
    &amp;lt;div className="flex items-center justify-center text-lg font-bold text-gray-700 rounded-full bg-white h-20 w-20 "&amp;gt;
      75%
    &amp;lt;/div&amp;gt;
  ),
};

export const CustomColors = Template.bind({});
CustomColors.args = {
  progressStart: 50,
  progressEnd: 100,
  barColor: "#10B981",
  className: "bg-gray-200",
  children: (
    &amp;lt;div className="flex items-center justify-center text-lg font-bold text-gray-700 rounded-full bg-slate-400 h-20 w-20"&amp;gt;
      75%
    &amp;lt;/div&amp;gt;
  ),
};

export const SlowSpeed = Template.bind({});
SlowSpeed.args = {
  progressStart: 0,
  progressEnd: 90,
  speed: 50,
  barColor: "#F59E0B",
  children: (
    &amp;lt;div className="flex items-center justify-center text-lg font-bold text-gray-700 rounded-full bg-slate-400 h-20 w-20"&amp;gt;
      {}
    &amp;lt;/div&amp;gt;
  ),
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example creates a story for a ProgressBar that wraps a child and show a progress. component has been shown three different states default, custom color and custom speed. &lt;/p&gt;

&lt;p&gt;Start the Storybook server. You can use the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm run storybook&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will start the Storybook&lt;/p&gt;

&lt;p&gt;A screenshot of storybook dashboard, and how you can customise component with props.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbqd4m2tz859zl5orcmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbqd4m2tz859zl5orcmz.png" alt="A demo of how it looks like on storybook dashboard." width="800" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>storybook</category>
      <category>frontend</category>
      <category>nextjs</category>
      <category>documentation</category>
    </item>
  </channel>
</rss>
