<?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: Sang Huynh Thanh</title>
    <description>The latest articles on DEV Community by Sang Huynh Thanh (@sanghuynhthanh).</description>
    <link>https://dev.to/sanghuynhthanh</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%2F1061121%2F01d1ab5b-ecfc-40b8-a2c7-78107eced9da.jpeg</url>
      <title>DEV Community: Sang Huynh Thanh</title>
      <link>https://dev.to/sanghuynhthanh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sanghuynhthanh"/>
    <language>en</language>
    <item>
      <title>Sidekiq vs GoodJob: A Comprehensive Comparison</title>
      <dc:creator>Sang Huynh Thanh</dc:creator>
      <pubDate>Mon, 07 Apr 2025 08:35:45 +0000</pubDate>
      <link>https://dev.to/sanghuynhthanh/sidekiq-vs-goodjob-a-comprehensive-comparison-mpk</link>
      <guid>https://dev.to/sanghuynhthanh/sidekiq-vs-goodjob-a-comprehensive-comparison-mpk</guid>
      <description>&lt;p&gt;In the Rails ecosystem, job processing libraries are essential for managing background tasks efficiently. We will compare their features, usability, and suitability for different project needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GoodJob&lt;/strong&gt; offers a single README.md file that is densely packed with information, which may overwhelm some users. In contrast, &lt;strong&gt;Sidekiq&lt;/strong&gt; utilizes GitHub's Wiki feature, providing easier navigation and topic-specific searches. While GoodJob's documentation is comprehensive, it can be challenging to find specific information compared to the better readability and ease of use found in Sidekiq.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batches and Callbacks
&lt;/h2&gt;

&lt;p&gt;When it comes to batch processing, Sidekiq and GoodJob take distinctly different approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sidekiq&lt;/strong&gt; excludes batch features from its free version. Users must upgrade to Pro or Enterprise plans to access this functionality, which may pose a barrier for smaller projects or teams with limited budgets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GoodJob&lt;/strong&gt; includes batch processing by default, allowing users to implement this feature without additional costs. This community-driven gem eliminates the need for extra plugins or upgrades.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features Comparison
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition&lt;/strong&gt;: In Sidekiq Pro, batches refer to collections of jobs monitored as a group. You can create a set of jobs to execute in parallel and then execute a callback when all jobs are finished. GoodJob tracks a set of jobs and enqueues an optional callback job when all the jobs have finished (succeeded or discarded).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jobs can be added to an existing batch?&lt;/strong&gt; Yes for both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access batch information&lt;/strong&gt;: Yes for both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Callback with statuses:&lt;/strong&gt; Sidekiq uses statuses like complete, success, and death. GoodJob uses finish, success, and discard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linger and Expiration:&lt;/strong&gt; Sidekiq's batch data lingers in Redis for 24 hours and expires after 30 days. In contrast, GoodJob ensures that batch information persists in the database before processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled Jobs:&lt;/strong&gt; Sidekiq does not have built-in support for scheduled jobs without upgrading. GoodJob supports both CLI and async execution modes and includes a dedicated Cron tab for managing scheduled tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Web UI Management
&lt;/h2&gt;

&lt;p&gt;Both libraries offer web interfaces with distinct features:&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Support authentication and authorization.&lt;/li&gt;
&lt;li&gt;Offer a polling feature for job status updates.&lt;/li&gt;
&lt;li&gt;Provide comprehensive dashboards for monitoring job queues.&lt;/li&gt;
&lt;li&gt;Allow micromanagement capabilities such as retrying and discarding jobs.&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%2Fmbg3bpp1o65bliwazmw7.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%2Fmbg3bpp1o65bliwazmw7.png" alt="GoodJob Dashboard" width="800" height="528"&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%2Fy7y19ccv0gdwx88f8bgx.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%2Fy7y19ccv0gdwx88f8bgx.png" alt="Sidekiq Dashboard" width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Unique Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Sidekiq supports many identity providers (e.g., Google, GitHub), while GoodJob offers basic authentication with role-based access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filtering:&lt;/strong&gt; Sidekiq does not support filtering from the UI, whereas GoodJob provides various filtering options by queue, job type, and parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process or Thread Information:&lt;/strong&gt; Available in Sidekiq but not in GoodJob.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reschedule Job?:&lt;/strong&gt; Yes in GoodJob; no in Sidekiq.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Error Handling and Dead Job Management
&lt;/h2&gt;

&lt;p&gt;Both libraries effectively handle errors during job execution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Both catch exceptions during job execution and log errors with details.&lt;/li&gt;
&lt;li&gt;They support automatic retries on failure with configurable limits.&lt;/li&gt;
&lt;li&gt;Both integrate with Rails logger for customizable logging.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Dead Job Handling
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sidekiq&lt;/strong&gt; moves failed jobs to a separate "Dead Job Queue" after exhausting all retry attempts and automatically prunes dead jobs after six months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GoodJob&lt;/strong&gt; keeps dead jobs in the same database as other job records, making them easier to manage using standard SQL tools. It allows custom error handling through ActiveJob callbacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Job Prioritization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sidekiq
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Allows multiple queues for effective priority management.&lt;/li&gt;
&lt;li&gt;Jobs are processed based on queue priority; within a single queue, they follow FIFO (First In, First Out) order.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GoodJob
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Supports multiple queues with a priority attribute for granular control.&lt;/li&gt;
&lt;li&gt;Considers both queue and job priority when selecting which job to run next.&lt;/li&gt;
&lt;li&gt;In both libraries, a higher-priority queue will process its jobs before lower-priority queues. However, this strategy can lead to potential issues; if a job within a high-priority queue is suspended or fails, it can cause all lower-priority queues to wait indefinitely.&lt;/li&gt;
&lt;li&gt;Within a single queue, jobs with higher priority will execute before those with lower priority. This feature provides more granular control over job execution timing, allowing less urgent tasks to wait until resources are available.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data Retention and Cleanup
&lt;/h2&gt;

&lt;p&gt;GoodJob uses a database for long-term storage with configurable retention periods. In contrast, Sidekiq primarily uses Redis, which automatically expires data after completion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Differences
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Storage Mechanism:&lt;/strong&gt; Sidekiq relies on ephemeral Redis storage while GoodJob uses a database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical Data Management:&lt;/strong&gt; GoodJob is better suited for maintaining longer-term job history compared to Sidekiq's focus on current tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration Flexibility:&lt;/strong&gt; GoodJob offers more detailed control over job record retention compared to Sidekiq's simpler setup.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Both Sidekiq and GoodJob offer robust job processing capabilities but cater to different needs within the Ruby on Rails ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sidekiq&lt;/strong&gt; is ideal for teams seeking straightforward setups willing to invest in premium features. Its reliance on Redis allows quick access but limits long-term data retention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GoodJob&lt;/strong&gt;, embracing an open-source philosophy, provides comprehensive features without additional costs. Its use of PostgreSQL allows better long-term data management, making it suitable for applications requiring detailed historical insights.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ultimately, your choice between Sidekiq and GoodJob should align with your project requirements, budget considerations, and desired level of control over job processing and data management. Evaluate your unique needs to determine which library fits best with your workflow and objectives.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>goodjob</category>
      <category>sidekiq</category>
      <category>rails</category>
    </item>
    <item>
      <title>Grafana K6: Load testing for awareness of your infrastructure</title>
      <dc:creator>Sang Huynh Thanh</dc:creator>
      <pubDate>Mon, 07 Apr 2025 08:19:38 +0000</pubDate>
      <link>https://dev.to/sanghuynhthanh/grafana-k6-load-testing-for-awareness-of-your-infrastructure-4hf</link>
      <guid>https://dev.to/sanghuynhthanh/grafana-k6-load-testing-for-awareness-of-your-infrastructure-4hf</guid>
      <description>&lt;p&gt;The versatile open-source tool for robust load testing. Learn how to stress-test your systems and build scalable applications that can handle real-world traffic demands.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Grafana K6?
&lt;/h2&gt;

&lt;p&gt;Grafana K6 is a modern load-testing tool that allows developers and QA engineers to test the reliability and performance of their systems. It's designed to catch performance regressions and problems early in the development cycle, helping teams build applications that can scale effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Resource Consumption&lt;/strong&gt;: Optimized for running high-load tests efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript-based Scripting&lt;/strong&gt;: Uses ES6 modules, allowing for modular and reusable test scripts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible Configuration&lt;/strong&gt;: Supports various load patterns and test scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensible&lt;/strong&gt;: Can be integrated with other tools and platforms for comprehensive testing and monitoring.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started with K6
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;For macOS users, K6 can be easily installed. (Note: Installation instructions for other operating systems can be found on the &lt;a href="https://grafana.com/docs/k6/latest/set-up/install-k6/" rel="noopener noreferrer"&gt;official K6 documentation&lt;/a&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding K6 Terminology
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VUs (Virtual Users)&lt;/strong&gt;: Simulated users generating traffic in your tests. More VUs generally mean more simulated traffic.&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;K6 runs multiple iterations in parallel using virtual users. Each VU is essentially a while(true) loop, executing your test script repeatedly for the duration of the test.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  K6 Test Lifecycle
&lt;/h2&gt;

&lt;p&gt;A typical K6 test goes through several stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialization&lt;/strong&gt;: Setting up test configuration and resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Preparing the test environment (optional).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution&lt;/strong&gt;: Running the actual test scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teardown&lt;/strong&gt;: Cleaning up after the test (optional).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Types of Load Tests with K6
&lt;/h2&gt;

&lt;p&gt;K6 supports various types of load tests, each designed to assess different aspects of your system's performance:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Average-Load Testing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: To understand if your system meets performance goals under normal conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Determine the average workload your system can handle&lt;/li&gt;
&lt;li&gt;Verify performance standards after system changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Configuration:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const options = {
  stages: [
    { duration: '5m', target: 100 }, // Ramp-up
    { duration: '30m', target: 100 }, // Sustain
    { duration: '5m', target: 0 }, // Ramp-down
  ],
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example result:&lt;/strong&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%2F0cs4nzglrp7f9q1omk1a.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%2F0cs4nzglrp7f9q1omk1a.png" alt="Result for K6 average load testing" width="697" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Stress Testing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Assess system performance under heavier-than-usual loads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After running average-load tests&lt;/li&gt;
&lt;li&gt;To verify stability during peak usage periods (e.g., holidays, special events)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Configuration:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const options = {
  stages: [
    { duration: '10m', target: 200 }, // Ramp-up
    { duration: '30m', target: 200 }, // Sustain
    { duration: '5m', target: 0 }, // Ramp-down
  ],
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Reuse your average-load test script, but increase the load or VU count.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Soak Testing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Check for performance degradation over extended periods.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Longer duration (e.g., 3, 8, 24, or even 72 hours)&lt;/li&gt;
&lt;li&gt;Monitor backend resources (RAM, CPU, Network, cloud resource growth)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Configuration:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const options = {
  stages: [
    { duration: '5m', target: 100 }, // Ramp-up
    { duration: '8h', target: 100 }, // Sustain
    { duration: '5m', target: 0 }, // Ramp-down
  ],
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Spike Testing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Simulate sudden, massive increases in traffic.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Rapid increase to extremely high loads&lt;/li&gt;
&lt;li&gt;Brief or no plateau period&lt;/li&gt;
&lt;li&gt;Fast ramp-down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Configuration:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const options = {
  stages: [
    { duration: '2m', target: 2000 }, // fast ramp-up to a high point
    // No plateau
    { duration: '1m', target: 0 }, // quick ramp-down to 0 users
  ],
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Tests may not be completed due to system overload&lt;/li&gt;
&lt;li&gt;Useful for assessing recovery times and behavior during critical overloads&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices and Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use Tags:&lt;/strong&gt; Group metrics for dynamic URLs to improve analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine Criteria:&lt;/strong&gt; Utilize Checks and Thresholds for comprehensive testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose the Right Load Model:&lt;/strong&gt; Decide between modeling load by VU count or iterations per second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep it Simple:&lt;/strong&gt; Stick to straightforward load patterns (ramp-up, plateau, ramp-down) for clear results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diversify Your Tests:&lt;/strong&gt; No single test type eliminates all risks, so use a combination of testing methods.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Grafana K6 is a versatile and powerful tool for load-testing modern applications. By incorporating various testing methods – from average-load to spike testing – you can ensure your systems are robust, scalable, and ready to handle real-world traffic patterns. Remember, the key to effective load testing is not just running the tests but also analyzing the results and continuously improving your system based on those insights.&lt;/p&gt;

&lt;p&gt;Start integrating K6 into your development and QA processes today, and take a significant step towards building more resilient and performant applications.&lt;/p&gt;

</description>
      <category>k6</category>
      <category>loadtesting</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Kamal-Proxy: Simplifying Zero-Downtime Deployments</title>
      <dc:creator>Sang Huynh Thanh</dc:creator>
      <pubDate>Mon, 07 Apr 2025 07:24:39 +0000</pubDate>
      <link>https://dev.to/sanghuynhthanh/kamal-proxy-simplifying-zero-downtime-deployments-27l7</link>
      <guid>https://dev.to/sanghuynhthanh/kamal-proxy-simplifying-zero-downtime-deployments-27l7</guid>
      <description>&lt;p&gt;Discover how the custom reverse proxy enhances Kamal’s deployment strategy with seamless traffic routing, zero-downtime updates, and automated security measures for containerized application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to Kamal-Proxy
&lt;/h2&gt;

&lt;p&gt;Kamal, a straightforward deployment tool built around Docker containers, has introduced its custom proxy called Kamal-Proxy. This new component aims to simplify the deployment process, particularly for newcomers or those less familiar with complex technological setups. In this post, we'll explore what Kamal-Proxy is and why it's an essential part of Kamal's deployment strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kamal-Proxy?
&lt;/h2&gt;

&lt;p&gt;Kamal-Proxy is a custom-built reverse proxy designed specifically for Kamal's deployment needs. It serves as a critical component in managing traffic to deployed applications, handling multiple services, and ensuring smooth, zero-downtime deployments. Unlike its predecessor Traefik, Kamal-Proxy is tailored to work seamlessly with Kamal's imperative approach to deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does Kamal need a proxy like Kamal-Proxy?
&lt;/h2&gt;

&lt;p&gt;Kamal-Proxy addresses these gaps by offering key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity in Service Deployment&lt;/strong&gt;: Kamal-Proxy maintains Kamal’s ease-of-use, allowing users to deploy services by specifying the service type and target without the need for complex configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Downtime Deployments&lt;/strong&gt;: While Kamal's basic approach doesn’t include automatic container replacement, Kamal-Proxy ensures zero-downtime updates by intelligently routing traffic during deployments, minimizing service disruption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic Management and Multi-Service Support&lt;/strong&gt;: Kamal-Proxy intelligently routes incoming requests to the appropriate containers, even when hosting multiple applications on the same server. It eliminates port conflicts by using hostnames for routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Monitoring&lt;/strong&gt;: To ensure reliable service, Kamal-Proxy continuously monitors container health, directing traffic only to healthy instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic TLS Certificates&lt;/strong&gt;: Security is enhanced with automatic issuance and management of TLS certificates, ensuring encrypted communication without manual intervention by utilizing &lt;strong&gt;&lt;em&gt;Let’s Encrypt.&lt;/em&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Kamal-Proxy Functionalities
&lt;/h2&gt;

&lt;p&gt;Kamal-Proxy integrates seamlessly with Kamal's deployment process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Traffic Interception&lt;/strong&gt;: It intercepts incoming client requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent Routing&lt;/strong&gt;: Based on predefined criteria (like hostnames), it routes requests to the appropriate containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update Management&lt;/strong&gt;: During deployments, it gradually shifts traffic from old to new containers, ensuring continuous service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health-Based Routing&lt;/strong&gt;: By monitoring container health, it automatically adjusts routing to maintain service reliability.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Imperative vs. Declarative?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Declarative (Traefik)&lt;/strong&gt;:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Specifies the desired end state (expected result), and the system figures out how to achieve that state.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# You declare what you want, not how to achieve it

http:
  routers:
    my-router:
      rule: "Host(`example.com`)"
      service: my-service
services:
  my-service:
    loadBalancer:
      servers:
        - url: "http://localhost:8080"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Imperative (Kamal-Proxy)&lt;/strong&gt;:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Provide step-by-step instructions to achieve the desired outcome.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have more direct control over each action.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# You explicitly state each action to take

kamal proxy:add-route example.com --target localhost:8080
kamal proxy:enable-ssl example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Removal of Host-Level Complexities
&lt;/h2&gt;

&lt;p&gt;Kamal-Proxy eliminates the need for "special cord files" that Traefik required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;: Traefik, being declarative, needed a way to understand the health status of containers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Workaround&lt;/strong&gt;: Kamal had to create special files (referred to as "cord files") on the host system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These files acted as a bridge between Kamal's understanding of container health and Traefik's configuration.&lt;/li&gt;
&lt;li&gt;Example: A file might be created or removed to signal whether a container is healthy or not.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Drawback&lt;/strong&gt;: This solution was seen as inelegant and added complexity to the host system.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This change simplifies the system architecture and makes deployments more straightforward, benefiting both new and experienced users.&lt;/p&gt;

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

&lt;p&gt;Kamal-Proxy represents a significant advancement in Kamal's deployment toolkit. By providing a custom-built proxy solution that aligns perfectly with Kamal's philosophy of simplicity and direct control, it enhances the deployment experience for users of all expertise levels. From simplifying service management to ensuring zero-downtime updates and automatic security measures, Kamal-Proxy embodies Kamal's commitment to making container deployment accessible and efficient.&lt;/p&gt;

&lt;p&gt;Understanding Kamal-Proxy is key to leveraging the full potential of Kamal for your deployment needs, offering a streamlined, secure, and user-friendly approach to managing containerized applications in today's dynamic digital landscape.&lt;/p&gt;

</description>
      <category>kamal</category>
      <category>deployment</category>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>How to generate a PDF file in Rails using the Grover gem?</title>
      <dc:creator>Sang Huynh Thanh</dc:creator>
      <pubDate>Sat, 08 Apr 2023 04:03:42 +0000</pubDate>
      <link>https://dev.to/sanghuynhthanh/how-to-generate-a-pdf-file-in-rails-using-the-grover-gem-56ie</link>
      <guid>https://dev.to/sanghuynhthanh/how-to-generate-a-pdf-file-in-rails-using-the-grover-gem-56ie</guid>
      <description>&lt;h2&gt;
  
  
  How to generate a PDF file in Rails using the Grover gem?
&lt;/h2&gt;

&lt;p&gt;When it comes to generating PDF files from HTML templates, there are several libraries and tools available. In this article, we'll take a closer look at the &lt;strong&gt;Grover&lt;/strong&gt; gem and discuss why we chose it over other alternatives like &lt;strong&gt;Prawn&lt;/strong&gt; and &lt;strong&gt;wicked_pdf&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A modern way to Generate PDF Files in Rails
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenges with wicked_pdf
&lt;/h3&gt;

&lt;p&gt;Initially, we used &lt;strong&gt;wicked_pdf&lt;/strong&gt; in some of our ongoing projects, and it worked well in most cases, enabling us to generate PDF files from our templates successfully. However, we did face some challenges with certain Bootstrap features like the &lt;strong&gt;&lt;code&gt;col&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;row&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;flexbox&lt;/code&gt;&lt;/strong&gt; styles, which were not fully supported by the library. We were able to work around this issue by changing the current view implementation to using a table instead of a flexbox or grid. However, this is not an ideal solution, as it limits the flexibility of the view.&lt;/p&gt;

&lt;p&gt;Additionally, we discovered that wicked_pdf is built on top of &lt;strong&gt;wkhtmltopdf&lt;/strong&gt;, an open-source command-line tool that uses the &lt;strong&gt;Webkit&lt;/strong&gt; rendering engine to convert HTML files to PDF and various image formats. However, this tool was archived as of &lt;strong&gt;January 2, 2023&lt;/strong&gt;, which made us reconsider our choice of using wicked_pdf.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing Grover
&lt;/h3&gt;

&lt;p&gt;This led us to explore other PDF generation tools and libraries, and we discovered the &lt;strong&gt;Grover&lt;/strong&gt; gem. Grover is a Ruby gem that uses &lt;strong&gt;&lt;a href="https://github.com/puppeteer/puppeteer" rel="noopener noreferrer"&gt;Google Puppeteer&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://www.chromium.org/Home" rel="noopener noreferrer"&gt;Chromium&lt;/a&gt;&lt;/strong&gt; to convert HTML into PDF, PNG, or JPEG files. To use Grover, you will need to install both the Grover gem and the puppeteer library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of Grover and also limitations of wicked_pdf
&lt;/h3&gt;

&lt;p&gt;One of the key advantages of Grover is its reputation and reliable community. It has been widely adopted by the Rails community due to its ease of use and versatility. Grover supports the &lt;strong&gt;latest CSS3 features&lt;/strong&gt; and JavaScript libraries, making it a popular choice for complex PDF conversions.&lt;/p&gt;

&lt;p&gt;Another advantage of Grover is its ability to render dynamic content, which is crucial for web applications that generate documents on the fly. This is achieved through Google Puppeteer, which provides a headless Chrome/Chromium environment to execute JavaScript and render dynamic content.&lt;/p&gt;

&lt;p&gt;In contrast, wicked_pdf is a popular alternative for PDF generation in Rails, but it has limitations in terms of supporting the latest CSS3 and JavaScript features. Additionally, it may require additional configuration and setup for rendering dynamic content, which could be time-consuming.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customization options
&lt;/h3&gt;

&lt;p&gt;Grover offers a range of configuration options to control the PDF layout, including page size, orientation, margin, and header/footer content. Moreover, it allows you to inject custom CSS and JavaScript code into the HTML to further customize the PDF output. This provides a high degree of flexibility in terms of customization.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Methods for Generating PDF Files using Grover in Rails&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Generating PDF files in Rails is a common task, and Grover is one of the gems that can help us accomplish it. In this blog post, we will explore two different methods for generating PDF files using Grover in Rails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render from template
&lt;/h3&gt;

&lt;p&gt;Rendering a template is a common approach to generating PDF files in Rails. This method involves creating an HTML template that includes placeholders for dynamic data or a custom template without a header or side navigation. Grover can then merge the template with the data to generate a PDF file.&lt;/p&gt;

&lt;p&gt;One advantage of this approach is that it provides complete control over the layout and formatting of the PDF output. This approach is particularly useful for generating documents such as invoices, receipts, and reports that require a specific layout and design.&lt;/p&gt;

&lt;p&gt;To render a template with Grover, you can use the &lt;strong&gt;&lt;code&gt;render_to_string&lt;/code&gt;&lt;/strong&gt; method in the Rails controller. This method takes a view template and any necessary data and returns the HTML code as a string. However, this code only contains relative URLs for stylesheets, images, and other assets. As a result, there may be issues with missing stylesheets and fonts when generating PDFs using Grover. In such cases, it is important to set the &lt;strong&gt;&lt;code&gt;display_url&lt;/code&gt;&lt;/strong&gt; property correctly. This is because Grover defaults to using &lt;strong&gt;&lt;code&gt;example.com&lt;/code&gt;&lt;/strong&gt; as the host, which can cause errors when attempting to access stylesheets and fonts from other sites.&lt;/p&gt;

&lt;p&gt;To prevent or resolve this issue, pass the relative HTML code as a string, along with any necessary data, to the &lt;strong&gt;&lt;code&gt;Grover::HTMLPreprocessor&lt;/code&gt;&lt;/strong&gt; helper. This will convert it into HTML code with all absolute URLs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;relative_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PayslipController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render_to_string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="ss"&gt;template: &lt;/span&gt;&lt;span class="s1"&gt;'controller/view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;layout: &lt;/span&gt;&lt;span class="s1"&gt;'my_layout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;payroll: &lt;/span&gt;&lt;span class="n"&gt;payroll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;address: &lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;absolute_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="no"&gt;Grover&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTMLPreprocessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relative_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'http://my.server/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'http'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also have the second option that involves specifying a &lt;code&gt;display_url&lt;/code&gt; that will replace the default configuration of the &lt;code&gt;example.com&lt;/code&gt; host, which can cause the CORS error when the page is displaying on another host and is different from our host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Grover&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relative_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;display_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to_pdf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rendering from a URL
&lt;/h3&gt;

&lt;p&gt;This approach involves specifying a URL to a web page along with the user’s cookies to make sure that Grover has permission to access the page we need to print. This method is useful for generating PDFs from existing web pages, such as product pages, blog posts, or help documentation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Grover&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;cookies: &lt;/span&gt;&lt;span class="n"&gt;header_cookies&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_pdf&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;header_cookies&lt;/span&gt;
  &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Cookie'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'; '&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;domain: &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Host'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Comparison, which one should I use?
&lt;/h3&gt;

&lt;p&gt;In this section, we'll take a closer look at each method and explore its strengths and weaknesses to help you determine which approach is best suited for your particular use case.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Rendering from a &lt;strong&gt;Template&lt;/strong&gt;
&lt;/th&gt;
&lt;th&gt;Rendering from a &lt;strong&gt;URL&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Advantages&lt;/td&gt;
&lt;td&gt;- Fine-grained control over the content and layout of the PDF document &lt;br&gt;- Can easily incorporate dynamic data and logic &lt;br&gt;- Easier to manage dependencies, such as stylesheets and fonts &lt;br&gt;- Can be faster and more efficient than rendering from a URL&lt;/td&gt;
&lt;td&gt;- Allows for generating PDFs from existing web pages, such as product pages, blog posts, or help documentation &lt;br&gt;- Supports rendering of single-page applications (SPAs) and other JavaScript-heavy websites &lt;br&gt;- Can print any page without specifying the data needed for that page &lt;br&gt;- Grover takes care of all the rendering for you &lt;br&gt;- Print pages outside the application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limitations&lt;/td&gt;
&lt;td&gt;- Requires development and maintenance of the template &lt;br&gt;- Can be less flexible and adaptable than rendering from a URL &lt;br&gt;- Need to prepare the data required for the rendering template&lt;/td&gt;
&lt;td&gt;- Requires both Puppeteer and Chromium to be installed on the server &lt;br&gt;- Requires passing user's cookies or session data for authorization &lt;br&gt;- May not work if cookies have expired or the user is not authenticated &lt;br&gt;- May require additional configuration to ensure security and reliability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I encountered some significant issues when implementing this feature, please take a look to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No-sandbox Puppeteer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When using Grover with AWS, there may be a need to run Puppeteer in &lt;strong&gt;no-sandbox&lt;/strong&gt; mode, which can cause security issues. The &lt;strong&gt;no-sandbox&lt;/strong&gt; mode disables the sandboxing mechanism of Chromium that isolates the browser from the operating system, which means that malicious code can potentially harm the system. &lt;/p&gt;

&lt;p&gt;So we need to create a dedicated user for export purposes only, but this can be difficult to configure when switching to this user. This approach involves creating a new user on the server with restricted permissions and allowing only the necessary access to perform the export task. This can ensure better security for the system, but it requires careful configuration and maintenance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authorization with cookies&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is because some websites use cookies to store session data or other information, which is necessary for rendering certain pages correctly. Without these cookies, &lt;strong&gt;&lt;code&gt;Grover&lt;/code&gt;&lt;/strong&gt; may not be able to access the necessary resources or data, which can result in incomplete or inaccurate PDF output. (The user will receive a PDF file about the login page instead of what they actually need).&lt;/p&gt;

&lt;p&gt;When rendering pages synchronously, passing the user's cookies to &lt;strong&gt;&lt;code&gt;Grover&lt;/code&gt;&lt;/strong&gt; is usually straightforward. However, when pushing the job to a worker, it may be more challenging to pass the cookies along with the request. This is because workers typically operate in a separate process or thread from the main application, which may not have access to the user's cookies or session data. Rather than that, what happens if the cookie expired at the time the job is executing, how can we retry this job without s newly created cookie?&lt;/p&gt;

&lt;h2&gt;
  
  
  Render as a background job
&lt;/h2&gt;

&lt;p&gt;To achieve better performance and scalability, it may be necessary to render the PDF as a background job. This is especially true when dealing with many PDF files within a zip file. However, this requires handling the job as a service rather than within the controller, as it is an asynchronous service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering from a URL
&lt;/h3&gt;

&lt;p&gt;One of the challenges of using the URL to render the PDF is ensuring that cookies still work when executing the job, no one can ensure that, maybe the user logout the account before the job execution or the cookies has been expired.&lt;/p&gt;

&lt;p&gt;It’s hard to resolve this problem, we can have some tries to work around this problem, we take it into account but it seems these solutions will do harm to our application&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customize some middleware to completely allow access when exporting PDF&lt;/li&gt;
&lt;li&gt;Create a super user that has privileges to access all pages&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rendering from a template
&lt;/h3&gt;

&lt;p&gt;Rendering from a template can be a good alternative for rendering PDFs as a background job, as it doesn't have any significant issues. However, one remaining challenge is that the controller's helper methods may be missing when rendering from a template, and this needs to be done manually.&lt;/p&gt;

&lt;p&gt;To overcome this challenge, we can use the &lt;strong&gt;&lt;code&gt;view_context&lt;/code&gt;&lt;/strong&gt; and include the required helpers to access them for rendering the PDF. However, this method can be cumbersome and complicated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActionView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EwaPayroll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;:PayrollsController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;view_paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url_helpers&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;PayrollHelper&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;FontAwesome&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IconHelper&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Pundit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fortunately, Rails has the built-in feature called &lt;strong&gt;&lt;code&gt;ActionController::Renderer&lt;/code&gt;&lt;/strong&gt; can be an efficient and scalable solution for rendering PDFs as a background job. With this feature, arbitrary templates can be rendered without being in controller actions or helper concerns. This eliminates the complexity of manually handling the controller's context and helper methods.&lt;/p&gt;

&lt;p&gt;To use &lt;strong&gt;&lt;code&gt;ActionController::Renderer&lt;/code&gt;&lt;/strong&gt;, simply call the &lt;strong&gt;&lt;code&gt;render&lt;/code&gt;&lt;/strong&gt; method on the &lt;strong&gt;&lt;code&gt;renderer&lt;/code&gt;&lt;/strong&gt; object, passing in the desired template name. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;PayrollsController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;template: &lt;/span&gt;&lt;span class="s1"&gt;'...'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In combination with the Grover gem in Rails, this approach can provide a solid solution for rendering PDFs in the background.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
