<?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: David Muñoz</title>
    <description>The latest articles on DEV Community by David Muñoz (@damuz91).</description>
    <link>https://dev.to/damuz91</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%2F595008%2F39f92790-5fe0-44b5-aef6-2d4ad746a400.jpeg</url>
      <title>DEV Community: David Muñoz</title>
      <link>https://dev.to/damuz91</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/damuz91"/>
    <language>en</language>
    <item>
      <title>Fixing the ElevenLabs Call Transfer Bug with Twilio</title>
      <dc:creator>David Muñoz</dc:creator>
      <pubDate>Wed, 01 Oct 2025 15:53:38 +0000</pubDate>
      <link>https://dev.to/damuz91/fixing-the-elevenlabs-call-transfer-bug-with-twilio-20ck</link>
      <guid>https://dev.to/damuz91/fixing-the-elevenlabs-call-transfer-bug-with-twilio-20ck</guid>
      <description>&lt;h2&gt;
  
  
  A note on frustration
&lt;/h2&gt;

&lt;p&gt;For more than &lt;strong&gt;three months&lt;/strong&gt;, I’ve been reporting this bug to ElevenLabs, along with several other developers who have asked for a fix. Unfortunately, it hasn’t been resolved, and the only practical way forward is to apply this kind of workaround.&lt;/p&gt;

&lt;p&gt;It’s frustrating to patch around such a core issue, but until it’s fixed, this manual solution is the only way to deliver a smooth caller experience.&lt;/p&gt;

&lt;p&gt;Special thanks to &lt;strong&gt;Simon&lt;/strong&gt;, who pointed me in the right direction and helped unlock the proper approach to solving this cutoff bug. 🙏&lt;/p&gt;




&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;When building &lt;strong&gt;voice agents with ElevenLabs&lt;/strong&gt; integrated with &lt;strong&gt;Twilio&lt;/strong&gt; (either through a &lt;strong&gt;SIP trunk&lt;/strong&gt; or via &lt;strong&gt;native Twilio integration&lt;/strong&gt;), you often need the agent to &lt;strong&gt;transfer a call&lt;/strong&gt; or &lt;strong&gt;end a call gracefully&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The problem: &lt;strong&gt;there’s a bug in ElevenLabs&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When the agent executes a &lt;strong&gt;call transfer&lt;/strong&gt;, the call is &lt;strong&gt;cut off immediately&lt;/strong&gt;, even before the agent finishes speaking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The same happens when the agent &lt;strong&gt;ends a call&lt;/strong&gt;—the cutoff is abrupt and unnatural.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ruins the experience: the agent might say &lt;em&gt;“Please hold while I transfer you…”&lt;/em&gt; but the line goes dead in the middle of the sentence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this happens
&lt;/h2&gt;

&lt;p&gt;Internally, ElevenLabs doesn’t handle the timing of call control well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When &lt;code&gt;transfer&lt;/code&gt; or &lt;code&gt;end_call&lt;/code&gt; is triggered, the audio stream is cut right away.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There’s no native way to delay the cutoff until after the agent finishes speaking.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this issue occurs in &lt;strong&gt;both cases&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SIP trunk integration with ElevenLabs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Native Twilio integration with ElevenLabs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The solution: implement &lt;code&gt;transfer_call&lt;/code&gt; and &lt;code&gt;end_call&lt;/code&gt; manually
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Important limitation:&lt;br&gt;
This fix only works for numbers added through Twilio’s native integration (Programmable Voice).&lt;br&gt;
If your numbers are connected via a SIP Trunk, Twilio does not allow updating or redirecting calls through the API. That’s why you’ll still get a 404 error when trying to apply this workaround on SIP Trunk calls.&lt;br&gt;
(Reference: Twilio docs — Programmable Voice calls can be updated via API, SIP Trunk calls cannot.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To fix this, you need to &lt;strong&gt;take control away from ElevenLabs&lt;/strong&gt; and handle the call lifecycle yourself.&lt;/p&gt;

&lt;p&gt;The approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Disable the native ElevenLabs tools&lt;/strong&gt; for transfer and end call.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create &lt;strong&gt;custom tools&lt;/strong&gt; in your backend:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;transfer_call&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;end_call&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt; When ElevenLabs invokes one of these tools:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Immediately return &lt;code&gt;200 OK&lt;/code&gt;&lt;/strong&gt; so ElevenLabs thinks everything worked and keeps the audio stream alive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enqueue a background job with a short delay&lt;/strong&gt; (2–5 seconds).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That job uses the &lt;strong&gt;Twilio REST API&lt;/strong&gt; to perform the real action:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update the call with a new TwiML &lt;code&gt;&amp;lt;Dial&amp;gt;&lt;/code&gt; for transfers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Or mark the call as &lt;code&gt;completed&lt;/code&gt; to hang up gracefully.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way, the agent can finish the spoken message before the transfer or termination actually happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: &lt;code&gt;transfer_call&lt;/code&gt; in Ruby on Rails
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Job to trigger the transfer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PhoneAi::TransferCallJob &amp;lt; ActiveJob::Base
  queue_as :default

  def perform(call_sid, destination)
    client = Twilio::REST::Client.new

    Rails.logger.info "📞 [TransferCallJob] call_sid=#{call_sid}, destination=#{destination}"

    result = client.calls(call_sid).update(
      url: Rails.application.routes.url_helpers.transfer_twiml_voice_calls_url(
        destination: destination,
        host: "my.host.com" # Make sure you have declared your route accordingly.
      ),
      method: "POST"
    )

    Rails.logger.info "✅ [TransferCallJob] Transfer success: SID=#{result.sid}, Status=#{result.status}"
  rescue Twilio::REST::RestError =&amp;gt; e
    Rails.logger.error "❌ [TransferCallJob] Twilio error: #{e.message} (code=#{e.code})"
    raise
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TwiML endpoint for transfers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Voice::CallsController &amp;lt; ApplicationController
  def transfer_twiml
    destination = params[:destination]

    response = Twilio::TwiML::VoiceResponse.new do |r|
      r.dial(number: destination)
    end

    render xml: response.to_s
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example: &lt;code&gt;end_call&lt;/code&gt; in Ruby on Rails
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Job to end the call
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PhoneAi::EndCallJob &amp;lt; ActiveJob::Base
  queue_as :default

  def perform(call_sid)
    client = Twilio::REST::Client.new

    Rails.logger.info "📞 [EndCallJob] Ending call - call_sid=#{call_sid}"
    # You must specify in your system prompt to use this tool after saying goodbye; or you can use the job and delay strategy again so it does not get cutoff.
    result = client.calls(call_sid).update(status: "completed")

    Rails.logger.info "✅ [EndCallJob] Call ended: SID=#{result.sid}, Status=#{result.status}"
  rescue Twilio::REST::RestError =&amp;gt; e
    Rails.logger.error "❌ [EndCallJob] Twilio error: #{e.message} (code=#{e.code})"
    raise
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How to use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ElevenLabs announces: &lt;em&gt;“Thank you for calling, goodbye.”&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It triggers &lt;code&gt;end_call&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your Rails backend responds immediately with &lt;code&gt;200 OK&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2 seconds later, the job executes &lt;code&gt;update(status: "completed")&lt;/code&gt; and the call ends naturally.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Benefits of this approach
&lt;/h2&gt;

&lt;p&gt;✔️ The agent &lt;strong&gt;finishes speaking&lt;/strong&gt; before the line is cut.&lt;br&gt;&lt;br&gt;
✔️ You control the &lt;strong&gt;delay&lt;/strong&gt; precisely (2s, 5s, etc).&lt;br&gt;&lt;br&gt;
✔️ Works for both &lt;strong&gt;transfers&lt;/strong&gt; and &lt;strong&gt;call termination&lt;/strong&gt;.  &lt;/p&gt;




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

&lt;p&gt;If you’re integrating &lt;strong&gt;ElevenLabs Voice AI with Twilio&lt;/strong&gt;, you’ll likely encounter this bug: &lt;strong&gt;transfers and call endings are cut off too early&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The fix is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implement your own tools&lt;/strong&gt; (&lt;code&gt;transfer_call&lt;/code&gt; and &lt;code&gt;end_call&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Delegate the actual logic to Twilio&lt;/strong&gt; using its REST API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Insert a small delay&lt;/strong&gt; so the agent can finish speaking before the action executes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you full control of the call flow and dramatically improves the caller’s experience.&lt;/p&gt;

</description>
      <category>elevenlabs</category>
      <category>twilio</category>
      <category>ai</category>
      <category>siptrunk</category>
    </item>
    <item>
      <title>How to Connect ElevenLabs with Twilio Using SIP Trunk (Not Webhooks)</title>
      <dc:creator>David Muñoz</dc:creator>
      <pubDate>Mon, 22 Sep 2025 12:59:26 +0000</pubDate>
      <link>https://dev.to/damuz91/connecting-elevenlabs-3md1</link>
      <guid>https://dev.to/damuz91/connecting-elevenlabs-3md1</guid>
      <description>&lt;h1&gt;
  
  
  How to Connect ElevenLabs with Twilio Using SIP Trunk (Not Webhooks)
&lt;/h1&gt;

&lt;p&gt;When integrating &lt;strong&gt;voice AI agents&lt;/strong&gt; with telephony, most tutorials point you toward the native &lt;strong&gt;Twilio Webhooks&lt;/strong&gt; integration. While this works, it’s not always the most flexible or reliable option.  &lt;/p&gt;

&lt;p&gt;In this article, I’ll show you how to connect &lt;strong&gt;ElevenLabs&lt;/strong&gt; with &lt;strong&gt;Twilio&lt;/strong&gt; using &lt;strong&gt;Elastic SIP Trunking&lt;/strong&gt;, giving you full control over call routing without relying on webhook callbacks.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Use SIP Trunk Instead of Webhooks?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lower latency:&lt;/strong&gt; SIP trunking provides a more direct media path.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stability:&lt;/strong&gt; No dependency on webhook round trips.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility:&lt;/strong&gt; Full control over authentication, routing, and encryption.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatibility:&lt;/strong&gt; Works like a standard VoIP trunk, so you can reuse the same setup in other systems.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Configure Twilio Elastic SIP Trunk
&lt;/h2&gt;

&lt;p&gt;Go to your Twilio Console and navigate to &lt;strong&gt;Elastic SIP Trunking&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  General Section
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Create New SIP Trunk&lt;/strong&gt; and give it a &lt;strong&gt;Friendly Name&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Keep all default options except:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Call Transfer (SIP Refer):&lt;/strong&gt; &lt;code&gt;Set caller ID as transferee&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable PSTN Transfer&lt;/strong&gt; → enabled
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Save and continue to &lt;strong&gt;Termination&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Termination Section
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Termination SIP URI&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set your termination domain.
&lt;/li&gt;
&lt;li&gt;Twilio will create it as:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; your-domain.pstn.twilio.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IP Access Control List&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;+&lt;/strong&gt; to add a new list.
&lt;/li&gt;
&lt;li&gt;Friendly Name: &lt;code&gt;elevenlabs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add the IP range:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 34.49.132.122/32
 34.49.132.122-34.49.132.122
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Credentials List&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;+&lt;/strong&gt; to create a username and password.
&lt;/li&gt;
&lt;li&gt;Save these credentials — you’ll need them in ElevenLabs.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjust the rest according to your case.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Origination Section
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;+&lt;/strong&gt; to add a new Origination URI.
&lt;/li&gt;
&lt;li&gt;Enter:  sip:sip.rtc.elevenlabs.io:5060;transport=tcp&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority:&lt;/strong&gt; &lt;code&gt;10&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weight:&lt;/strong&gt; &lt;code&gt;10&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enabled:&lt;/strong&gt; checked  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optionally enable &lt;strong&gt;CNAM Lookup&lt;/strong&gt;.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Numbers Section
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Assign a phone number to your SIP trunk.
&lt;/li&gt;
&lt;li&gt;You can buy a new one or use an existing number.
&lt;/li&gt;
&lt;li&gt;Only numbers listed here will work under this SIP Trunk.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;✅ Twilio setup is now complete. Next, let’s configure ElevenLabs.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Configure ElevenLabs SIP Trunk
&lt;/h2&gt;

&lt;p&gt;In the ElevenLabs dashboard, go to &lt;strong&gt;Phone Numbers&lt;/strong&gt; → &lt;strong&gt;Import Number → From SIP Trunk&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Information
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Label:&lt;/strong&gt; give it a friendly name.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phone number:&lt;/strong&gt; choose the same number you added in Twilio’s &lt;strong&gt;Numbers&lt;/strong&gt; section.
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Inbound Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Media Encryption:&lt;/strong&gt; &lt;code&gt;Allowed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SIP Trunk username:&lt;/strong&gt; the username you created in Twilio
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SIP Trunk password:&lt;/strong&gt; the password you created in Twilio
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Outbound Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Address:&lt;/strong&gt; your Twilio termination domain, e.g.: your-domain.pstn.twilio.com&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport Type:&lt;/strong&gt; &lt;code&gt;TLS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media Encryption:&lt;/strong&gt; &lt;code&gt;Allowed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SIP Trunk username:&lt;/strong&gt; same as Twilio
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SIP Trunk password:&lt;/strong&gt; same as Twilio
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Assign an Agent
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Once the number is imported, open its configuration in ElevenLabs.
&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;AI agent&lt;/strong&gt; that should handle calls on this number.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that’s it! Your ElevenLabs agent is now connected to Twilio via SIP trunking. 🎉  &lt;/p&gt;




&lt;h2&gt;
  
  
  Testing the Integration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Place a call to your Twilio number.
&lt;/li&gt;
&lt;li&gt;Verify that the call is routed to ElevenLabs and handled by the assigned agent.
&lt;/li&gt;
&lt;li&gt;Check audio quality and logs to ensure SIP authentication is working.
&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;By connecting ElevenLabs with Twilio using &lt;strong&gt;SIP trunking&lt;/strong&gt;, you gain a more reliable, flexible, and scalable integration compared to the webhook approach. This method allows you to:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Securely authenticate calls with SIP credentials.
&lt;/li&gt;
&lt;li&gt;Enable encrypted media (TLS + SRTP).
&lt;/li&gt;
&lt;li&gt;Assign numbers to specific AI agents with full control.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re building production-ready AI voice agents, this SIP Trunk approach is the way to go. 🚀  &lt;/p&gt;




&lt;p&gt;💬 Have questions or ran into issues? Drop a comment below or reach out — happy to help!&lt;/p&gt;

</description>
      <category>elevenlabs</category>
      <category>twilio</category>
      <category>ai</category>
      <category>siptrunk</category>
    </item>
    <item>
      <title>Setting up Ruby on Rails with RVM, Puma, Mina, Nginx, Sidekiq and Redis on Amazon Linux 2</title>
      <dc:creator>David Muñoz</dc:creator>
      <pubDate>Tue, 29 Oct 2024 19:02:52 +0000</pubDate>
      <link>https://dev.to/damuz91/setting-up-ruby-on-rails-with-rvm-puma-mina-nginx-sidekiq-and-redis-on-amazon-linux-2-2f0m</link>
      <guid>https://dev.to/damuz91/setting-up-ruby-on-rails-with-rvm-puma-mina-nginx-sidekiq-and-redis-on-amazon-linux-2-2f0m</guid>
      <description>&lt;p&gt;After using for several years this same stack on Ubuntu, our AWS Expert recommended us to move to Amazon Linux 2 in order to take advantage of optimized hardware, pricing and other benefits of using their OS.&lt;/p&gt;

&lt;p&gt;The stack includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ruby (Installed using &lt;a href="https://rvm.io/" rel="noopener noreferrer"&gt;RVM&lt;/a&gt;, but you can use any other version manager, or compile yourself ruby)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nginx (As web server + reverse proxy to Puma server)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Puma (As rails app server)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NodeJs + Yarn (If your application needs to compile assets)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sidekiq (For background jobs processing)+ Redis (As persistence engine for Sidekiq). Since you are in AWS ecosystem you could also use &lt;a href="https://aws.amazon.com/elasticache/" rel="noopener noreferrer"&gt;Elasticache&lt;/a&gt; instead of local Redis, just parameterize correctly your Sidekiq if you want to do so.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mina (For deployments, but you could use Capistrano, or git clone/pull the project manually)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I made this same guide for Ubuntu (Thanks to Serhan Balcı for stealing my article. Please increase awareness of this guide by stealing it again and posting it as yours, lol). See:&lt;br&gt;
&lt;a href="https://damuz91.medium.com/setting-up-rails-app-in-ubuntu-machine-rvm-sidekiq-nginx-puma-capistrano-2329ee84511f" rel="noopener noreferrer"&gt;&lt;strong&gt;Setting up Rails app in Ubuntu machine (RVM + Sidekiq + Nginx + Puma + Capistrano)&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrading your system and installing dependencies
&lt;/h2&gt;

&lt;p&gt;The following updates the system, installs git, cmake, nginx and postgres client. Note that if you do not use postgres as database client of your app, you would have to use proper client libraries, for example mysql.&lt;/p&gt;

&lt;p&gt;Before copy and paste the following commands please be aware that it installs postgres libraries. If you don’t use postgres omit them. If you don’t want to accept the libraries installing right away remove the -y argument.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo yum update -y
sudo yum install curl git gcc make -y
sudo amazon-linux-extras install nginx1 postgresql11 -y
sudo yum install postgresql-devel postgresql-libs -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;In addition you can customize your hostname with, replace railsapp with how you want your host to be labeled:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo hostnamectl set-hostname railsapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Installing Ruby
&lt;/h2&gt;

&lt;p&gt;My personal choice is RVM, but you could also use other options or compile ruby from source yourself.&lt;/p&gt;

&lt;p&gt;Import RVM gpg key (You should use the key from &lt;a href="http://rvm.io" rel="noopener noreferrer"&gt;rvm.io&lt;/a&gt; site, not the one posted anywhere on internet for security reasons, be aware!):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg — keyserver keyserver.ubuntu.com — recv-key 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;After imported download and run the RVM installation script with:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sSL [https://get.rvm.io](https://get.rvm.io) | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then source it, note how my default user is ec2-user, if it is different change it to your user name. (Or exit and login back with SSH):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source /home/ec2-user/.rvm/scripts/rvm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now install your desired ruby (In my case I am using latest 2.x version, which is 2.7, but you can install the one that suits your app):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rvm install 2.7.0
rvm use 2.7.0 — default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now you can test it with ruby -v command:&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%2F6yb2yo1gv0t8uy0dveec.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%2F6yb2yo1gv0t8uy0dveec.png" width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Nginx
&lt;/h2&gt;

&lt;p&gt;Basically you got to place your app configuration inside a file that should be imported in the nginx conf. In order to do that my suggestion would be to create a folder, then create a file that contains the app configuration and then referencing it in nginx.conf file:&lt;/p&gt;

&lt;p&gt;Create the folder:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mkdir /etc/nginx/sites-enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Create the file:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/nginx/sites-enabled/app.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now this command initialized a file in the previous created folder named as app.conf, but it will only be saved once we fill in with contents and save it using nano, you can use your editor of preference. Place the following configuration and be sure to tweak it to your needs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Now lets include this configuration file in nginx.conf file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Inside the http block, include the following line:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;include /etc/nginx/sites-enabled/*.conf;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;So it looks like this:&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%2F348wb17foxooa9e1ra02.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%2F348wb17foxooa9e1ra02.png" alt="Got to love my MegamanX wallpaper" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition of our purposes this will tell nginx to include all configuration files inside the sites-enabled folder, so if you got more apps/pages you could include another app.conf file there and it will be loaded by nginx. Also you could add &lt;a href="https://stackoverflow.com/questions/21812360/what-is-the-difference-between-the-sites-enabled-and-sites-available-directo" rel="noopener noreferrer"&gt;sites-available folder and symlink your configuration files in sites-enabled&lt;/a&gt; but thats out of the scope of this guide, just a recommendation.&lt;/p&gt;

&lt;p&gt;Finally, and pay attention here, the nginx got to have access to the application’s path, so you either setup proper permissions OR run nginx as your ruby user, in this case &lt;strong&gt;ec2-user&lt;/strong&gt;. For educational purposes lets just scroll up to the first line of the &lt;strong&gt;nginx.conf&lt;/strong&gt; file and set and replace the user to *&lt;em&gt;ec2-user *&lt;/em&gt;(or the user you are using).&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user ec2-user;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Save and exit (With Ctrl + X if using nano).&lt;/p&gt;

&lt;p&gt;Enable your nginx service:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl enable nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Installing your Rails app
&lt;/h2&gt;

&lt;p&gt;Now, here is my suggested folder structure. It is friendly with mina and capistrano, also if you want to implement different stages like staging or production it would be work too, but you are free to use any.&lt;/p&gt;

&lt;p&gt;Note that if you change it you got to modify your app.conf file for nginx. Remember to reload nginx each time you modify those configuration files.&lt;/p&gt;

&lt;p&gt;The folders structure is:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;production
-&amp;gt; railsapp
--&amp;gt; current
--&amp;gt; releases
--&amp;gt; shared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Inside the current directory is where our Rails application would be. Lets create the structure and clone our project&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd
mkdir production
cd production
mkdir railsapp
cd railsapp
git clone xxx
mv xxx current
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;See how I renamed the folder project with just &lt;strong&gt;current&lt;/strong&gt;. If you &lt;strong&gt;cd current **and print your path it would show like this&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%2Fkjm299pjhtkd3vd4ho4p.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%2Fkjm299pjhtkd3vd4ho4p.png" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now lets install bundler and then install all our gems from our Gemfile:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem install bundler
gem update --system
gem update bundler
bundle install --without development test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;You might encounter errors in this process if your Gemfile contains gems that needs dependencies that we have not installed. So you got to closely check at errors in this step and look what dependencies are unmet. For example in our case we assumed that we were working with Postgres, so we installed in the first section of this guide the Postgres dev libraries, if we would have not done that then we would get an error in this step because our Gemfile contains the &lt;strong&gt;pg&lt;/strong&gt; gem. If you are using Mysql or other driver, you have to stop here and install the proper dependencies.&lt;/p&gt;

&lt;p&gt;But if your Gems list installed successfully then let’s continue by setting up Puma service as systemd service so we see our App in action.&lt;/p&gt;

&lt;p&gt;Note: If you are using &lt;a href="https://github.com/bkeepers/dotenv" rel="noopener noreferrer"&gt;dotenv&lt;/a&gt;, or you need to add further environment variables this is when. Create your .env.production file and fill it in now.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Puma
&lt;/h2&gt;

&lt;p&gt;We are setting up puma to run binding to a Unix socket, not TCP socket, here is a &lt;a href="https://blog.proxyone.eu/how-to-increase-the-puma-server-performance-4ac59547740b" rel="noopener noreferrer"&gt;good explanation on why it is a good idea&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Assuming you are using Puma and it was installed because it was located in your Gemfile, then you should have it ready to use. You can cd into your app folder and run &lt;strong&gt;puma&lt;/strong&gt; command and it will try to boot your application in development mode. But let’s install it as a systemd service.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/systemd/system/puma.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This will initialize a file in the system directory, which should include your puma configuration. Replace the paths and the user to your needs.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Save and exit (Ctrl + X if using Nano).&lt;/p&gt;

&lt;p&gt;Now enable it with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl enable puma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Before we can start our Puma service we need to make sure that our application contains proper configuration. See how the puma.service file refers to config/puma/production.rb file. You should modify that file accordingly, also note that this production file could be named different depending on the stage you are using, for example staging.rb if that is the case.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
The previous gist is quiet generic, in terms that the app_dir variable was set to a value were it contains the path of 3 levels up of the production.rb file, and that is because the app root is located in:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/home/ec2-user/production/current/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;But our puma production.rb configuration file is located in:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/home/ec2-user/production/current/config/puma/production.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;If this is not the case your place your Rails app root folder in app_dir. I just tried to make it generic.&lt;/p&gt;

&lt;p&gt;Also note that we send a &lt;strong&gt;true&lt;/strong&gt; to the daemonize method. Current puma version needs the daemonize option to be in that file and not in the service configuration. Depending on your puma version (Older versions needed this, as in my previous Ubuntu guide) you would have to remove that from the puma configuration file and just add the argument -d in the puma.systemd file at the end of the ExecStart command. As of writing of this guide latest Puma is 5.6.4 (Birdie’s version)&lt;/p&gt;

&lt;p&gt;Finally check that we are setting our puma logs to be written in log/* directory, we are needing to read those files later if we find any troubles.&lt;/p&gt;

&lt;p&gt;Make sure your project contains the puma configuration folder and now start the puma service:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service puma start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This should start your puma service. If it doesn’t check the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Your app has connection to your database, try to run the rails console in your project directory and execute any SQL query. Make sure you placed all your environment variables, if needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your puma configuration contains correct paths.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The version of your ruby matches the ruby you placed in your puma.system file, note that for this guide we are using 2.7.0&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check the puma.stderr.log and puma.stdout.log for any outputs that would prevent the app from starting (Like a sytanx error in your application, if this is the case the puma running in Development mode won’t catch any syntax error because it does not do a full scan of the source code, but in production the boot process does it)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check the output of jornalctl -u puma -f -n 100&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As a final check just go to your app folder, run puma -e productionand let it start. Then in a different ssh session run curl localhost and check the output of first ssh session.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now you should have puma running your Rails app. Stop it and let’s setup deployment with Mina.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service puma stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Setting up Redis (Optionally needed for Sidekiq)
&lt;/h2&gt;

&lt;p&gt;First you got to install Redis, fortunately our friend &lt;a href="https://gist.github.com/jpickwell" rel="noopener noreferrer"&gt;jpickwell&lt;/a&gt; created a super &lt;a href="https://gist.github.com/jpickwell/e6857da4ba17c83ef09729c5d448f6bb" rel="noopener noreferrer"&gt;simple script to install on AL2&lt;/a&gt;, just use it and it will do all, no questions, lol.&lt;/p&gt;

&lt;p&gt;Remember we are using local Redis, if you are in AWS ecosystem you might want to use Elasticache, if so you don’t have to install Redis, you just have to &lt;a href="https://github.com/mperham/sidekiq/wiki/Using-Redis" rel="noopener noreferrer"&gt;configure Sidekiq to use Redis&lt;/a&gt; on from Elasticache service, you would then skip this section of the guide and go to setup sidekiq. If that is not the case then keep going and by default Sidekiq will try to connect to local redis using REDIS_URL=redis://localhost:6379 .&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget -O redis_install.sh [https://gist.githubusercontent.com/jpickwell/e6857da4ba17c83ef09729c5d448f6bb/raw/67783f35b666afb38e82120781f7ceb75ef1f1f9/install-redis.sh](https://gist.githubusercontent.com/jpickwell/e6857da4ba17c83ef09729c5d448f6bb/raw/67783f35b666afb38e82120781f7ceb75ef1f1f9/install-redis.sh)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;(Or you could install it yourself, I got burnt trying to find out how to do a clean installation, leave in the comments if you find an easy way to do it using yum or whatever)&lt;/p&gt;

&lt;p&gt;Run the installation script:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo sh redis_install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Make sure it is running by typing redis-cli and the PING command:&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%2F7nivcr1rstxdinyqba2c.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%2F7nivcr1rstxdinyqba2c.png" alt="Checking that Redis is working" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now Redis is installed, we can now setup Sidekiq.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Sidekiq
&lt;/h2&gt;

&lt;p&gt;Assuming you got access to an Elasticache Redis instance OR you got Redis listening on localhost you should now setup the Sidekiq systemd configuration file.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/systemd/system/sidekiq.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now modify to your needs the following sidekiq configuration script&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Modify the user and the WorkingDirectory path. Make sure you have a config/sidekiq.yml file that contains your sidekiq configuration, for example it could be simple as:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:concurrency: 5
:queues:
- default
- critical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now enable it:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl enable sidekiq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;And start it:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service sidekiq start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;If it does not start then same as Puma troubleshooting you should check why would it be not starting, most likely a miss configuration in the boot time, like a missing environment variable or lack of database connection. If none of the above works try to run Sidekiq manually and let it output any errors by just stepping in your rails app root and running sidekiq .&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up NodeJS and Yarn (If your application needs assets precompile)
&lt;/h2&gt;

&lt;p&gt;If your application needs to compile your assets then you would have to install NodeJS and Yarn. If your Rails app is api-only you can skip this step and remove the precompilation task from deploy.rb file.&lt;/p&gt;

&lt;p&gt;First install the Node repo (I am placing here the URL but you should go into nodejs site and pick the repo URL from there)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sL https://rpm.nodesource.com/setup_16.x | sudo -E bash -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now install NodeJS:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo yum install nodejs -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Confirm with node -v&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%2Fx97l0jn26duvx0hwy95s.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%2Fx97l0jn26duvx0hwy95s.png" alt="If you know a better way of installing NodeJS please let me know in the comments" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now install Yarn repo:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sL https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;and install Yarn:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo yum install yarn -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;We will see how it works in the deployment script. If you omit previous steps Mina (or any) won’t be able to run rails assets:precompile task.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deployment with Mina
&lt;/h2&gt;

&lt;p&gt;First lets erase the project folder and let’s prepare the folder structure that Mina needs in order to make deployments.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd
rm -rf production/railsapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Now logout of your remote machine and go back to local. Setup the following deployment script. Place it in config/deploy.rb.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
You would have to edit the following variables:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;application_name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;domain: Put your remote ip or Url&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;deploy_to: Make sure it matches your app path&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;repository: The git repository (Make sure you have ssh access)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;user: The user which you use to connect with ssh&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;forward_agent: Leave it to true, but make sure your id_rsa.pub is included in remote’s machine ssh/authorized_keys file.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that is ready just run the mina setup command and it will create mina’s folder structure for your project using the parameters you setup before. Read more info on mina setup on their &lt;a href="https://github.com/mina-deploy/mina/blob/master/docs/getting_started.md" rel="noopener noreferrer"&gt;Getting started guide&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mina setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will ssh in to your remote machine and create the proper folder structure, now in case you are using dotenv you have to SSH back in to your remote machine and place the proper content of .env.production file in production/railsapp/shared/.env.production . This is because on deployment mina creates a symlink of this file in the current version’s folder of the project, so you don’t have to setup that file each time or even worst, upload your sensitive data in a repository. If you need mina to symlink more files or folders modify your shared_files variable in the deploy.rb script.&lt;/p&gt;

&lt;p&gt;Note how in the deploy.rb file we have a command to run assets precompilation, if your Rails app is api-only you can remove that line.&lt;/p&gt;

&lt;p&gt;Now you are ready, just run deploy and it will show you what troubles it encounters. Read them closely as almost all the times the problem is something quite obvious but one tend not to read the complete error description.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mina deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Bonus: Setup SSL with Lets encrypt and Certbot
&lt;/h2&gt;

&lt;p&gt;If you need to setup SSL for your project I highly recommend to use &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Lets Encrypt&lt;/a&gt; and &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt; to basically do all the work for you. (Love those guys, when I go billionaire I will send some churros, promise).&lt;/p&gt;

&lt;p&gt;Install EPEL packages:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo amazon-linux-extras install epel -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Install certbot for nginx:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo yum install -y certbot python2-certbot-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Go back to your /etc/nginx/sites-enabled/app.conf file and replace your server directive to use your secured URL:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server_name example.com www.example.com; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Save it, reload and check nginx configuration with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service nginx reload
nginx -t
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If all went good you are ready to run Certbot:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo certbot --nginx -d example.com -d [www.example.com](http://www.example.com)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Follow the instructions and you are ready to go!&lt;/p&gt;

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

&lt;p&gt;We have setup a Rails app with all the basic dependencies on Amazon Linux 2 machine, ready to run for a production environment. All the steps described are just the collection of steps that I have gathered through the years; some of them could seem hackish but I encourage you to improve this process by leaving in the comments any ideas on how to make this smoother.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>nginx</category>
      <category>sidekiq</category>
      <category>amazonlinux2</category>
    </item>
    <item>
      <title>Multiple deployments and High Availability with Mina and Ruby on Rails</title>
      <dc:creator>David Muñoz</dc:creator>
      <pubDate>Tue, 29 Oct 2024 18:57:52 +0000</pubDate>
      <link>https://dev.to/damuz91/multiple-deployments-and-high-availability-with-mina-and-ruby-on-rails-59nc</link>
      <guid>https://dev.to/damuz91/multiple-deployments-and-high-availability-with-mina-and-ruby-on-rails-59nc</guid>
      <description>&lt;p&gt;Recently we realized that we needed a &lt;a href="https://en.wikipedia.org/wiki/High-availability_cluster" rel="noopener noreferrer"&gt;High Availability&lt;/a&gt; model on our infrastructure with some of our Monolithic and Micro services projects on Ruby on Rails.&lt;/p&gt;

&lt;p&gt;That means we needed, in terms of code delivery, to make same project version deployment into multiple machines at once.&lt;/p&gt;

&lt;p&gt;However this model is generic to any client-server / monolithic / micro services approach and to any languages and frameworks. In my project I use &lt;a href="https://github.com/mina-deploy/mina" rel="noopener noreferrer"&gt;Mina&lt;/a&gt; (Formerly using &lt;a href="https://capistranorb.com/" rel="noopener noreferrer"&gt;Capistrano&lt;/a&gt;), so that means that on each deployment the script makes a SSH-in to the remote machine and performs the deployment process: Git clone, Git pull, rake db:migrate assets:precompile, puma:restart, etc… Before using Capistrano I was doing all this manually #sigh.&lt;/p&gt;

&lt;p&gt;Fortunately Mina allowed to do all this just by setting up the deployment script through the deploy.rb file, now the next challenge would be on how to do deployment on many remote machines using the same deployment script (without modifying it each time or running multiple commands).&lt;/p&gt;

&lt;h2&gt;
  
  
  Mina or Capistrano?
&lt;/h2&gt;

&lt;p&gt;Why do I use Mina instead of Capistrano? I won’t write it here since &lt;a href="https://infinum.com/blog/faster-web-application-deployments-using-mina-instead-of-capistrano/" rel="noopener noreferrer"&gt;Infinum &lt;/a&gt;guys already did a great explanation and comparison:&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%2F7f3elwh8fd8jx9empm6j.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%2F7f3elwh8fd8jx9empm6j.png" alt="Thanks to Infinum (link to article above)" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But even so, this solution would fit Capistrano deployment script too. See also:&lt;br&gt;
&lt;a href="https://damuz91.medium.com/setting-up-rails-app-in-ubuntu-machine-rvm-sidekiq-nginx-puma-capistrano-2329ee84511f" rel="noopener noreferrer"&gt;&lt;strong&gt;Setting up Rails app in Ubuntu machine (RVM + Sidekiq + Nginx + Puma + Capistrano)&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  There is already a Mina multi deploy gem
&lt;/h2&gt;

&lt;p&gt;The guys at &lt;a href="https://github.com/codica2/mina-multideploy" rel="noopener noreferrer"&gt;Codica&lt;/a&gt; managed to solve this problem with mina-multideploy gem, but quickly I found a problem that wouldn’t let me continue use it, so instead of creating the issue, look around for a solution and doing a PR… 😅 I did a little of scripting myself using Mina tasks and achieved to do inline (one at a time, one after the other) deployments using just a few ruby lines.&lt;/p&gt;

&lt;p&gt;Problem with mina-multideploy is that they use a parallelism model and if one fails nothing happens; not even a warning or an error output, that could lead to unwanted results if you are using a HA infrastructure model, since different app versions would be running over the same load balancer and that would be 🤯 to troubleshoot (Trust me, been there).&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%2Fcci0tcefq8nzwh6pvhjx.gif" 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%2Fcci0tcefq8nzwh6pvhjx.gif" alt="Don’t get me wrong, I love this gem, it just didn’t work for me this time" width="493" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can try yourself by creating a &lt;a href="https://github.com/mina-deploy/mina-docs/blob/master/source/tasks/deploy_force_unlock.md" rel="noopener noreferrer"&gt;deploy.lock&lt;/a&gt; file (Using mina, or manually creating it in the remote’s server path) on one of the remote machines and then running the bundle exec rails multideploy:start to do deployment on all the target machines; you will see the deployment success, however we had a remote machine deploy-locked 👎&lt;/p&gt;

&lt;p&gt;See also:&lt;br&gt;
&lt;a href="https://damuz91.medium.com/setting-up-ruby-on-rails-with-rvm-puma-mina-nginx-sidekiq-and-redis-on-amazon-linux-2-4241960ecd0" rel="noopener noreferrer"&gt;&lt;strong&gt;Setting up Ruby on Rails with RVM, Puma, Mina, Nginx, Sidekiq and Redis on Amazon Linux 2&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;This solution is environment friendly, in our case we will use production as our environment but we can declare staging too.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1
&lt;/h3&gt;

&lt;p&gt;Let’s assume that you have your deploy.rb with generic settings, mine are:&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%2F6duujka9wqv8jpvce1w5.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%2F6duujka9wqv8jpvce1w5.png" alt="A generic mina settings deployment file" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2
&lt;/h3&gt;

&lt;p&gt;Now simply declare a hash of your desired environments with useful info inside of each one; if you wish you could just leave the urls array, in my case each of the stages need to restart different amount of Sidekiq services and should compile assets only in production:&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%2F02tgb1wzmtudo97hb84t.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%2F02tgb1wzmtudo97hb84t.png" alt="Declaring the stages and their properties" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3
&lt;/h3&gt;

&lt;p&gt;Now let’s declare a task named as each of our environments. If you have more options for each of your stage this is the step where to add variables that will be considered later in our deploy task:&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%2Fq3ipf0pspwic5uce6ex1.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%2Fq3ipf0pspwic5uce6ex1.png" alt="Declaring helper variables per stage" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4
&lt;/h3&gt;

&lt;p&gt;Now we should simply write our deploy task. Note that we only invoke the assets_precomiple task if the assets_precompile variable is set to true. Same with Sidekiq services were i got them listed in my remote machines as Sidekiq1, Sidekiq2, … If you don’t have any custom settings per stage just remove those specific command lines.&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%2Fhdezjckdyuukx66z6ni7.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%2Fhdezjckdyuukx66z6ni7.png" alt="The deploy task" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally we just create our multideploy task:&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%2Fvocvfl5f5bcfxvk2bokl.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%2Fvocvfl5f5bcfxvk2bokl.png" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can just run mina production multideploy and it will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Loop through the urls array that we set in the first step&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For each URL in the production stage it will set the domain and the rails_env .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Invoke the deploy task that will also execute additional invocations per our options that we declared in our stage setting step.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;Now if we want to connect to our console over a specific stage we can invoke the console by overwritting the console task:&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%2Fbxmrp6cp9jqjq0fruhvj.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%2Fbxmrp6cp9jqjq0fruhvj.png" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will grab the first value of the urls array and will invoke the old reliable console task. Since we are using a HA strategy all our remote machines should be the same (supposedly). We can now call mina production console .&lt;/p&gt;

&lt;p&gt;Our final deployment script would look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Since we are looping through an array of remote machines IPs and doing each deployment inline what happens if 1 of them fails? The answer is that the deployment will raise an exception and you will have a nice Error output from our deployment script, now we need to implement a way to safely rollback all of our remote machines to a stable project’ s version. Please leave in the comments any ideas on how to achieve this.&lt;/p&gt;

&lt;h2&gt;
  
  
  High Availability with AWS
&lt;/h2&gt;

&lt;p&gt;Now that we have managed our multiple deployment strategy using Mina (But also reproducible on Capistrano) we should setup a load balancer, in my case with AWS is just a few clicks away; I will not go deep into this since there is a lot out there on how to do it, also if you are not in AWS ecosystem you could setup your load balancer using any other service, like Nginx which allows you to make a simple load balancer with a &lt;a href="https://nginx.org/en/docs/http/load_balancing.html" rel="noopener noreferrer"&gt;few lines of code&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1
&lt;/h3&gt;

&lt;p&gt;Create a target group:&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%2Fgf7lmtiaz5p11dhrq5z0.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%2Fgf7lmtiaz5p11dhrq5z0.png" alt="First step of target group creation" width="800" height="259"&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%2F757uuebmep2gxoa3e8u9.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%2F757uuebmep2gxoa3e8u9.png" alt="Register your targets (The machines you want to include under your HA)" width="800" height="340"&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%2Fukl8t6wu3xs9pt2iwwkz.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%2Fukl8t6wu3xs9pt2iwwkz.png" alt="Review and confirm" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2
&lt;/h3&gt;

&lt;p&gt;Create a load balancer&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%2F1o8cpzelwgqnxqb8q4ix.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%2F1o8cpzelwgqnxqb8q4ix.png" alt="Make sure you select internet-facing" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the target group we created on step 1:&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%2F6bb5rlfvhkgo2aqsmigx.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%2F6bb5rlfvhkgo2aqsmigx.png" alt="Selecting the target group were it will balance all incoming requests" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on create and make sure your rules are in place. Once provisioned you should be ready to test your load balancer URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3
&lt;/h3&gt;

&lt;p&gt;Make your DNS point your app URL to the load balancer address.&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%2Fwt2qo2jccobbaihie9iy.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%2Fwt2qo2jccobbaihie9iy.png" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thats pretty much it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final notes
&lt;/h2&gt;

&lt;p&gt;High availability goes beyond of having multiple machines being balanced over all of the requests, but this is a first step that you should consider once you need to guarantee uptime, resources usage optimization, failover strategies and so others on your project.&lt;/p&gt;

&lt;p&gt;Please leave in the comments any ideas, questions or ways to improve this topic.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>capistrano</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Cómo desplegar una app de Ruby on Rails en Ubuntu 22</title>
      <dc:creator>David Muñoz</dc:creator>
      <pubDate>Tue, 29 Oct 2024 18:49:02 +0000</pubDate>
      <link>https://dev.to/damuz91/como-desplegar-una-app-de-ruby-on-rails-en-ubuntu-22-f36</link>
      <guid>https://dev.to/damuz91/como-desplegar-una-app-de-ruby-on-rails-en-ubuntu-22-f36</guid>
      <description>&lt;h3&gt;
  
  
  TLDR;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Desplegar una aplicación en Ruby on Rails en producción es un proceso que cada vez que se hace tiene un problema diferente. De esto depende las gemas, las dependencias y el sistema operativo que se use. Veremos como se despliega en Ubuntu 22 siendo la base más sencilla sobre la que se puede operar, con gemas estándar y con un código fuente de ejemplo que incluyo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para este tutorial utilizaremos DigitalOcean como proveedor de servicios en la nube.&lt;/p&gt;

&lt;p&gt;Usando &lt;a href="https://www.youtube.com/redirect?event=video_description&amp;amp;redir_token=QUFFLUhqbmxtOGZQSWV6MWpVM1NMdERvLTJranRVc1MzZ3xBQ3Jtc0traFc4bjd5bk5IWDg4M3N2Z0NqSEJ4NnVvbFdIemlDSC1wNlNHTVpya2JXTjBOMS1JYmZXZ1pCR2hNTTRfYS1yY214dzhPelpYOFNqTGpaTmc5UVNidWtnQnRpcWxfa3pMWkJvMEZndlZYM0NYejI0bw&amp;amp;q=https%3A%2F%2Fm.do.co%2Fc%2Fd55e17bf64cc&amp;amp;v=1jJPGfWQ-rA" rel="noopener noreferrer"&gt;este link obtienes un bono de $200 en DigitalOcean&lt;/a&gt; para que puedas hacer este tutorial y usarlo en futuros despliegues.&lt;/p&gt;

&lt;p&gt;Nota: &lt;a href="https://www.youtube.com/watch?v=1jJPGfWQ-rA" rel="noopener noreferrer"&gt;Este tutorial también está en Youtube&lt;/a&gt;, donde explico en formato de video el paso a paso y doy comentarios acerca de cada paso.&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%2F5x0xwbwj8inw4kpkvtt4.jpeg" 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%2F5x0xwbwj8inw4kpkvtt4.jpeg" width="800" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/c/estoyprogramando" rel="noopener noreferrer"&gt;EstoyProgramando&lt;/a&gt; es un canal en Youtube que enseña cómo incursionar en el mundo del desarrollo de software.&lt;/p&gt;

&lt;p&gt;Cualquier persona de cualquier carrera puede cambiar su profesión a programador debido a las múltiples facilidades que se encuentran disponibles, también aprovechando el mercado laboral que se encuentra en auge.&lt;/p&gt;

&lt;p&gt;Suscríbete a mi canal para más contenido de cómo iniciar en el mundo de la programación, además de encontrar contenido específico de programación en Ruby.&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2Aa5mN5GE5tIpuKotT" 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2Aa5mN5GE5tIpuKotT" width="126" height="20"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 1.
&lt;/h3&gt;

&lt;p&gt;Crea un servidor Ubuntu versión 22.04 (LTS). Recomiendo mínimo 1GB de RAM.&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%2Fkv3dy3f2tw01kkdu87mx.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%2Fkv3dy3f2tw01kkdu87mx.png" alt="Vista de creación de Droplets" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la sección de método de autenticación puedes escoger &lt;strong&gt;Password&lt;/strong&gt; por simplicidad, o puedes configurar tus llaves de acceso.&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%2Fnuwzhu7shd3soh71das4.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%2Fnuwzhu7shd3soh71das4.png" alt="Vista de métodos de autenticación" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Al final hacer click en Crear. Cuando se termine de crear ya podemos acceder por protocolo SSH al Droplet (es decir, al server)&lt;/p&gt;

&lt;p&gt;Abrimos la Terminal (o la aplicación de línea de comandos según el sistema operativo que se tenga) e ingresamos el comando de ssh con el usuario root y la ip del Droplet.&lt;/p&gt;

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

&lt;/div&gt;

&lt;p&gt;Ahora configuramos el usuario con el que vamos a operar, todos estos comandos deben ingresarse en la aplicación de Terminal con la sesión de SSH abierta en nuestro server remoto.&lt;/p&gt;

&lt;p&gt;En los siguientes comandos voy a crear mi usuario david, si tu usuario se llama diferente debes ajustar todos los comandos con el nombre del usuario adecuado.&lt;/p&gt;

&lt;p&gt;Crear el usuario:&lt;/p&gt;

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

&lt;/div&gt;

&lt;p&gt;Agregar el usuario al grupo de sudoers:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo usermod -aG sudo david
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Cambiamos desde el usuario root al usuario david:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;su david
# Pide la contraseña
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Cambiamos el nombre del host (Cambiar server por el nombre que quieras asignar al servidor):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo hostnamectl set-hostname server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Para ver los cambios en el nombre del servidor podemos salir con exit y reingresar con su david .&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 2.
&lt;/h3&gt;

&lt;p&gt;Con la sesión abierta y el usuario designado logueado vamos a actualizar el sistema e instalar las librerías necesarias:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install software-properties-common
sudo apt-get install libssl-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Ahora podemos instalar &lt;a href="https://rvm.io/" rel="noopener noreferrer"&gt;RVM&lt;/a&gt; (Aunque se puede usar cualquier otro manejador de versiones de Ruby)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\curl -sSL https://get.rvm.io | bash -s stable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Aquí debe salir un error, el cual pide que agregues la clave del autor, por razones de seguridad no coloco ese comando aquí.. pero puedes copiarlo y pegarlo. Una vez lo hagas corre de nuevo el comando anterior para instalar RVM.&lt;/p&gt;

&lt;p&gt;Ahora vamos a cargar el RVM con el comando:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source /home/david/.rvm/scripts/rvm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Así, el comando rvm ya estará disponible.&lt;/p&gt;

&lt;p&gt;Luego instalamos openssl:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rvm pkg install openssl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Y ahora si instalamos Ruby indicándole el directorio de OpenSSL:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rvm install 3.0.4 --with-openssl-dir=$HOME/.rvm/usr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;(En mi caso instalé la versión 3.0.4, pero según cuando estés haciendo este tutorial puede haber otra versión más reciente)&lt;/p&gt;

&lt;p&gt;Este comando debe demorarse varios minutos…&lt;/p&gt;

&lt;p&gt;Mientras esperas te recomiendo algunos de mis videos más populares:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/GXvL6Lkm8o8" rel="noopener noreferrer"&gt;📚 Tips para conseguir trabajo como programador&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/bi9QRJ6mjvs" rel="noopener noreferrer"&gt;📚 Debo estudiar ingeniería de sistemas?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/j0rf2vjGUpA" rel="noopener noreferrer"&gt;📚 Estudiar programación en el 2023&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/U_lWz4sEZ2o" rel="noopener noreferrer"&gt;📚 Cómo aprendí a programar&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/Fjs5J2dKKO8" rel="noopener noreferrer"&gt;📚 Por qué aprender Ruby?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/qhkHvBQRtQE" rel="noopener noreferrer"&gt;📚 Los programadores deben saber inglés&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/rBfmeUZPGK8" rel="noopener noreferrer"&gt;📚 Curso de fundamentos de programación&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora puedes indicarle al sistema operativo que use por defecto la versión de Ruby que acabamos de instalar, con el comando:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rvm use 3.0.4 --default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Si te da un error debes modificar el archivo ~/.bashrc:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# nano ~/.bashrc
# Puedes incluirlo al final del archivo:
source ~/.rvm/scripts/rvm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Luego de salir y regresar a la sesión del usuario ya el comando rvm use debería funcionar correctamente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 3
&lt;/h3&gt;

&lt;p&gt;Debes clonar el código del proyecto en cualquier parte de la carpeta de tu usuario. Por ejemplo, podemos usar este &lt;a href="https://github.com/damuz91/mi-bodega-rails" rel="noopener noreferrer"&gt;proyecto de ejemplo&lt;/a&gt; el cual es un pequeño sistema de información en Ruby on Rails 7 o puedes usar tu propio proyecto. Si usas tu propio proyecto debes considerar las dependencias específicas, por ejemplo si necesitas librerías adicionales para que las gemas de tu proyecto funcione.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/damuz91/mi-bodega-rails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;luego accedemos a la carpeta recién creada con el comando cd y corremos el comando bundle install . Aquí podrán salir errores si acaso el comando bundle no logró instalar las gemas listadas en el Gemfile, esto normalmente es porque harán falta algunas librerías para compilar las gemas necesarias.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.youtube.com/watch?v=JG0C2rtpCvI" rel="noopener noreferrer"&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%2Fovt16mlg79h11dumacex.jpg" alt="Cómo hacer un sistema de información en Ruby on Rails" width="480" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En este video hacemos el sistema que usamos de ejemplo en este tutorial.&lt;/p&gt;

&lt;p&gt;Los siguientes pasos corresponden a configuración específica del proyecto de ejemplo aunque podrían ser usados en tu caso también:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Borramos el credentials.yml.enc existente: rm config/credentials.yml.enc&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generamos un nuevo archivo de credenciales: Editor="nano --wait" bin/rails credentials:edit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creamos y migramos la base de datos: rails db:create db:migrate db:seed RAILS_ENV=production&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Los pasos 1 y 2 pueden no aplicar en tu caso si acaso el proyecto tiene credenciales encriptadas en el archivo y si cuentas o no con la clave correcta en el archivo master.key .&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 4
&lt;/h3&gt;

&lt;p&gt;Vamos a instalar Nginx para administrar las peticiones web entrantes:&lt;/p&gt;

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

&lt;/div&gt;

&lt;p&gt;Creamos el archivo donde colocaremos la configuración de nginx:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/nginx/sites-enabled/app.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Colocamos el siguiente script con cuidado de modificar la ruta adecuada según donde esté ubicada la carpeta de tu proyecto:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream app {
  server unix:///home/david/mi-bodega-rails/puma.sock fail_timeout=0;
}

server {
  server_name _;
  root /home/david/mi-bodega-rails/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  location / {
    proxy_pass http://app;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    # proxy_set_header X-Forwarded-Host 'www.railsapp.com';
    proxy_set_header X-Forwarded-Proto $scheme;
  }

  location ~ ^/(500|404|422).html {
    root /home/david/mi-bodega-rails/public;
  }

  error_page 500 502 503 504 /500.html;
  error_page 404 /404.html;
  error_page 422 /422.html;

  client_max_body_size 4G;
  keepalive_timeout 10;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Borramos la configuración por defecto anterior:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo rm /etc/nginx/sites-enabled/default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Corremos el servicio de nginx como nuestro usuario (Otra alternativa es dar permisos al usuario www-data de lectura y escritura a la carpeta del proyecto):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/nginx/nginx.conf
# Modificar la primera linea:
user david # En lugar de www-data. Coloca el nombre de tu usuario
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Reiniciamos nginx:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service nginx restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Ahora creamos el script para que el servidor de App PUMA corra en el Systemd:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/systemd/system/puma.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Y colocamos la siguiente configuración (Recuerda ajustarlo a tu usuario y directorio del proyecto):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
User=david
WorkingDirectory=/home/david/mi-bodega-rails
Environment=RAILS_ENV=production
ExecStart=/home/david/.rvm/gems/ruby-3.0.4/wrappers/bundle exec puma -C /home/david/mi-bodega-rails/config/puma/production.rb
Restart=always
KillMode=process

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Luego reiniciamos el servicio y activamos la nueva configuración:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl enable puma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Precompilamos los assets de nuestro proyecto:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /ruta/al/proyecto
bundle exec rails assets:precompile RAILS_ENV=production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;E iniciamos (o reiniciamos) el servidor de Puma:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service puma restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Y debería cargar la aplicación:&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%2F173p8tc3iwpumin0a1ig.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%2F173p8tc3iwpumin0a1ig.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cualquier error que surja se puede consultar en:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log/production.log
log/pumastdout.log
log/pumastderr.log
sudo journalctl -u puma -f -n 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Este mismo tutorial en versiónAmazon Linux 2 lo encuentras en mi perfil.&lt;/p&gt;

&lt;p&gt;Cualquier pregunta o comentario es bienvenido!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ubuntu</category>
      <category>nginx</category>
      <category>puma</category>
    </item>
  </channel>
</rss>
