<?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: Eduardo Reyes</title>
    <description>The latest articles on DEV Community by Eduardo Reyes (@ns01).</description>
    <link>https://dev.to/ns01</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%2F49208%2Ffa38e1a5-9e33-4d03-b4b2-2b411912a3c7.jpg</url>
      <title>DEV Community: Eduardo Reyes</title>
      <link>https://dev.to/ns01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ns01"/>
    <language>en</language>
    <item>
      <title>Keep Secrets Out of the State: Terraform's Ephemeral Resources</title>
      <dc:creator>Eduardo Reyes</dc:creator>
      <pubDate>Sat, 21 Jun 2025 00:56:30 +0000</pubDate>
      <link>https://dev.to/ns01/keep-secrets-out-of-the-state-terraforms-ephemeral-resources-2f21</link>
      <guid>https://dev.to/ns01/keep-secrets-out-of-the-state-terraforms-ephemeral-resources-2f21</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kyebjb18pvd2gk2jczx.webp" 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%2F0kyebjb18pvd2gk2jczx.webp" alt="The Cloud padlock, in the sky protecting us at all times." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing Sensitive Data in Terraform with Ephemeral Resources
&lt;/h2&gt;

&lt;p&gt;Terraform state files contain detailed information about your infrastructure, including sensitive data like passwords, API keys, and authentication tokens.&lt;/p&gt;

&lt;p&gt;While Terraform offers various methods to manage this sensitive information, the introduction of ephemeral resources in version 1.10+ provides a powerful new approach to keeping secrets out of your state files entirely.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore how ephemeral resources work, why they're important for security, and provide a practical example using AWS RDS database creation with automatic password rotation.&lt;/p&gt;

&lt;p&gt;Consider this, you've misconfigured the S3 bucket that holds your Terraform state, allowing for anonymous access, now everyone can see your sensitive data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you've configured your state S3 bucket with anonymous access you have bigger problems to worry about than what is written here.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your first priority with dealing with Terraform is securing your state file, you can use a well-protected S3 bucket, Terraform Cloud, Consul, and &lt;a href="https://developer.hashicorp.com/terraform/language/backend?ref=reyes.im" rel="noopener noreferrer"&gt;more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ephemeral resources are a kind of resource that is made to keep one-time data outside of the Terraform state, this is perfect for sensitive material – though there are some factors to consider.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Documentation&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/resources/ephemeral?ref=reyes.im" rel="noopener noreferrer"&gt;Ephemeral Resources&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/ephemeral?ref=reyes.im" rel="noopener noreferrer"&gt;Ephemeral Block Reference&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example
&lt;/h2&gt;

&lt;p&gt;Creating a database with &lt;a href="https://aws.amazon.com/rds/?ref=reyes.im" rel="noopener noreferrer"&gt;RDS&lt;/a&gt; is a common enough pattern, so let's use that to show how to use &lt;em&gt;ephemeral&lt;/em&gt; blocks to create a password with Terraform while avoiding having the secret stored in the Terraform state &lt;strong&gt;and&lt;/strong&gt; allowing us to rotate the secret with a simple version bump (or a time_rotating resource).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.10"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 6.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;random&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/random"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 3.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/time"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.3"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;versions.tf&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;ephemeral&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"database"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"time_rotating"&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rotation_minutes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;allocated_storage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;db_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awesome_db"&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;password_wo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ephemeral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
  &lt;span class="nx"&gt;password_wo_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;time_rotating&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unix&lt;/span&gt;
  &lt;span class="nx"&gt;skip_final_snapshot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awesome_username"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;main.tf&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's break down the example, first, the ephemeral resource is declared, it behaves exactly the same as a standard resource, the only difference is that its value is not known after apply by Terraform.&lt;/p&gt;

&lt;p&gt;Instead, it uses a &lt;code&gt;foo_version&lt;/code&gt; property to track if the ephemeral value needs to change – &lt;em&gt;once you change the version there's no way of getting the old value back.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  &lt;span class="c"&gt;# aws_db_instance.this will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + allocated_storage &lt;span class="o"&gt;=&lt;/span&gt; 10
      + copy_tags_to_snapshot &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
      + db_name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awesome_db"&lt;/span&gt;
      + engine &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
      + instance_class &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
      + password_wo &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;write-only attribute&lt;span class="o"&gt;)&lt;/span&gt;
      + password_wo_version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + region &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
      + skip_final_snapshot &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
      + username &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awesome_username"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# time_rotating.password will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"time_rotating"&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + rotation_minutes &lt;span class="o"&gt;=&lt;/span&gt; 10
      + unix &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

Plan: 2 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;terraform plan -out planfile (trimmed some data out of the output)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After the instance is created it won't detect changes for ten minutes (how we defined it in the time_rotating resource).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;terraform plan -out planfile&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, we'll let the time_rotating resource lapse by waiting ten minutes and check again with a new speculative plan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the
last &lt;span class="s2"&gt;"terraform apply"&lt;/span&gt; which may have affected this plan:

  &lt;span class="c"&gt;# time_rotating.password has been deleted&lt;/span&gt;
  - resource &lt;span class="s2"&gt;"time_rotating"&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2025-06-27T22:23:36Z"&lt;/span&gt;
      - unix &lt;span class="o"&gt;=&lt;/span&gt; 1751063616 -&amp;gt; null
        &lt;span class="c"&gt;# (9 unchanged attributes hidden)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

Unless you have made equivalent changes to your configuration, or ignored the
relevant attributes using ignore_changes, the following plan may include
actions to undo or respond to these changes.

─────────────────────────────────────────────────────────────────────────────

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-place&lt;/span&gt;

Terraform will perform the following actions:

  &lt;span class="c"&gt;# aws_db_instance.this will be updated in-place&lt;/span&gt;
  ~ resource &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      ~ password_wo_version &lt;span class="o"&gt;=&lt;/span&gt; 1751063616 -&amp;gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="c"&gt;# (72 unchanged attributes hidden)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# time_rotating.password will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"time_rotating"&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + rotation_minutes &lt;span class="o"&gt;=&lt;/span&gt; 10
    &lt;span class="o"&gt;}&lt;/span&gt;

Plan: 1 to add, 1 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;terraform plan -out planfile&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We can then apply the plan to rotate the database password with a new random one, &lt;code&gt;terraform apply -auto-approve planfile&lt;/code&gt;, rotating static db credentials is one way of using it, maybe not the most useful but it's one of the only resources that support &lt;code&gt;write-only&lt;/code&gt; attributes at the moment, but now you know how to use them, other providers might have better support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations and Considerations for Ephemeral Resources
&lt;/h3&gt;

&lt;p&gt;While ephemeral resources offer significant security benefits, there are some important limitations to keep in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limited Provider Support&lt;/strong&gt; : Currently, only certain providers and resources support write-only attributes, which are necessary for ephemeral resources to function properly. Always check provider documentation for compatibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging Challenges&lt;/strong&gt; : Since values aren't stored in state, debugging can be more difficult when issues arise with ephemeral resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;State Recovery&lt;/strong&gt; : If you need to import or recover infrastructure, ephemeral resources add complexity since their values aren't available in state backups.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Team Coordination&lt;/strong&gt; : When multiple team members work with infrastructure containing ephemeral resources, additional coordination may be needed since values can't be referenced from the state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requires Terraform 1.10+&lt;/strong&gt; : Older Terraform versions don't support this feature, so you'll need to ensure your environment is updated.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Despite these limitations, ephemeral resources provide a valuable security enhancement for handling sensitive data in Terraform when implemented appropriately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading:
&lt;/h3&gt;

&lt;p&gt;Ephemeral resources also bring ephemeral variables and outputs to the table, allowing you to pass ephemeral values to other modules or as a one-off variable when creating resources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/values/variables?ref=reyes.im#exclude-values-from-state" rel="noopener noreferrer"&gt;Input Variables&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/values/outputs?ref=reyes.im#ephemeral-avoid-storing-values-in-state-or-plan-files" rel="noopener noreferrer"&gt;Output Values&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also consider, that with ephemeral resources you are able to read from secret stores, configure some resource and "forgetting" the actual secret, think about the example we just did but instead of generating a random password we bring a new one from &lt;a href="https://aws.amazon.com/secrets-manager/?ref=reyes.im" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>terraform</category>
    </item>
    <item>
      <title>Laravel Requests data transformation</title>
      <dc:creator>Eduardo Reyes</dc:creator>
      <pubDate>Sat, 08 Feb 2020 15:33:08 +0000</pubDate>
      <link>https://dev.to/ns01/laravel-requests-data-transformation-1d9j</link>
      <guid>https://dev.to/ns01/laravel-requests-data-transformation-1d9j</guid>
      <description>&lt;p&gt;Few people know that you can piggyback on a &lt;a href="https://laravel.com/docs/master/validation#form-request-validation" rel="noopener noreferrer"&gt;Form Requests&lt;/a&gt; to transform the data you receive before getting it into the controller - this is super useful to keep controllers clean and to make validation easier.&lt;/p&gt;

&lt;p&gt;Imagine someone sends you a line-separated email list to your API without doing any client-side transformations on their side (this can easily happen by just sending the value of a textarea) so you’ll probably want to validate that data, and use it down the line in a format that’s easier for your program to handle (array/Collection).&lt;/p&gt;

&lt;p&gt;First thing we need to do is create our &lt;a href="https://laravel.com/docs/master/validation#form-request-validation" rel="noopener noreferrer"&gt;Form Request&lt;/a&gt; and our controller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:controller -i EmailsController
php artisan make:request UserEmailsRequest

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

&lt;/div&gt;



&lt;p&gt;this will create a new file under &lt;code&gt;App\Http\Requests&lt;/code&gt;, this file should be injected into your controller instead of the default request so that the validation and data transformations can happen auto-magically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Http\Controllers;

use App\Http\Requests\UserEmailsRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;

class EmailsController extends Controller
{
    /**
     * Handle the incoming request.
     *
     * @param UserEmailsRequest $request
     * @return JsonResponse
     */
    public function __invoke(UserEmailsRequest $request): JsonResponse
    {
        return response()-&amp;gt;json([], Response::HTTP_NOT_IMPLEMENTED);
    }
}

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

&lt;/div&gt;



&lt;p&gt;To use this controller in your &lt;code&gt;routes/api.php&lt;/code&gt; file just add it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::post('somewhere', 'EmailsController');

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;__invoke&lt;/code&gt; function in the controller gets called automatically by Laravel whenever you don’t specify a function to call.&lt;/p&gt;

&lt;p&gt;Now, to the interesting part - this are all the basic functions you’ll want to override in your form request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserEmailsRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
        ];
    }

    /**
     * Prepare the data for validation.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
    }
}

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$request-&amp;gt;emails&lt;/code&gt; looks like this &lt;code&gt;"foo@bar.com\nbar@foo.com,foobar@barfoo.com"&lt;/code&gt; and we want to transform it into an array and validate that each one of the values is also a valid email, the final array will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
    "foo@bar",
    "bar@foo.com",
    "foobar@barfoo.com",
]

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

&lt;/div&gt;



&lt;p&gt;We will start by transforming the data using the &lt;code&gt;prepareForValidation&lt;/code&gt; function, this is called before validation is even attempted as the name suggests, in here we can transform the request and validate against the transformed data (the original data is still available through the &lt;code&gt;$this-&amp;gt;get('emails')&lt;/code&gt; function)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
 * Prepare the data for validation.
 *
 * @return void
 */
protected function prepareForValidation()
{
    // This is where the magic happens, the data from the key 'emails' will be overwritten
    // with the return value of the formatEmails function.

    $this-&amp;gt;merge([
        'emails' =&amp;gt; $this-&amp;gt;formatEmails($this-&amp;gt;emails),
    ]);
}

/**
 * Transforms the request from a list of emails separated by a \n to an array of emails.
 *
 * @param string $emails
 *
 * @return array
 */
protected function formatEmails(string $emails): array
{
    // Simple transformation in here, explode the string by the delimiter, then filter
    // all the empty lines and then trim the empty values from the final array.
    // this is not production ready code so don't use it.

    return collect(explode("\n", $emails))
        -&amp;gt;filter(static function ($email) {
            return !empty($email);
        })
        -&amp;gt;map(static function ($email) {
            return trim($email);
        })-&amp;gt;toArray();
}

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

&lt;/div&gt;



&lt;p&gt;Before the transformation, validating the data that was sent to us would have required to do the same transformation we just did as a custom rule, now since the data is nicely packed into a standard array we can use standard validation rules against it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'emails' =&amp;gt; 'required|array',
            'emails.*' =&amp;gt; 'required|email',
        ];
    }
}

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

&lt;/div&gt;



&lt;p&gt;And that’s it! if we go back to our controller we can use &lt;code&gt;$request-&amp;gt;emails&lt;/code&gt; as an array now plus all the values in the array will be validated using standard laravel rules&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Http\Controllers;

use App\Http\Requests\UserEmailsRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;

class EmailsController extends Controller
{
    /**
     * Handle the incoming request.
     *
     * @param UserEmailsRequest $request
     * @return JsonResponse
     */
    public function __invoke(UserEmailsRequest $request): JsonResponse
    {
       // do something here with your clean copy of $request-&amp;gt;emails remember that you
       // that you can get the original request values by doing $request-&amp;gt;get('emails')

        return response()-&amp;gt;json([
            ['emails' =&amp;gt; $request-&amp;gt;emails]
        ], Response::HTTP_OK);
    }
}

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

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "emails": [
        "foo@bar.com",
        "bar@foo.com",
        "foobar@barfoo.com"
    ]
}

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

&lt;/div&gt;



&lt;p&gt;The response from the endpoint will be a validated json array, just as we wanted! now down the line you can use this transformed value in other methods, your one-off function that handled the transformation is tucked away into the request itself and you don’t have to add noise to your controllers - if the function you made is something you’re able to re-use for other requests then make a base request controller, drop that function in there and inherit the requests from that base controller instead, that way you’ll have access to that function wherever you need it.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>Hugo and Nginx multi-stage build Dockerfile</title>
      <dc:creator>Eduardo Reyes</dc:creator>
      <pubDate>Sat, 16 Jun 2018 02:20:10 +0000</pubDate>
      <link>https://dev.to/ns01/hugo-and-nginx-multi-stage-build-dockerfile-3o63</link>
      <guid>https://dev.to/ns01/hugo-and-nginx-multi-stage-build-dockerfile-3o63</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://reyes.im/post/docker-hugo-image/" rel="noopener noreferrer"&gt;Reyes&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;After searching for a bit I was unable to find a nice pre-made Dockerfile to serve my personal site (built on top of &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt;), some of the images I found were only Hugo build steps, some others were able to serve and build the site but they pulled the &lt;code&gt;FROM:ubuntu&lt;/code&gt; docker anti-pattern.&lt;/p&gt;

&lt;p&gt;So here I'll describe what I'm doing on my final &lt;strong&gt;Dockerfile&lt;/strong&gt;, it's a really simple &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; &lt;a href="https://docs.docker.com/develop/develop-images/multistage-build/" rel="noopener noreferrer"&gt;Multi-Stage build&lt;/a&gt;, the first step gets the &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; binary and builds the site, the second one copies over the public folder of the built site and serves it using the official alpine &lt;a href="https://hub.docker.com/_/nginx/" rel="noopener noreferrer"&gt;Nginx image&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's a multi-stage build?
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image. To show how this works, Let’s adapt the Dockerfile from the previous section to use multi-stage builds.&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Hugo Build Step
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:3.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HUGO_VERSION 0.41&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HUGO_BINARY hugo_${HUGO_VERSION}_Linux-64bit.tar.gz&lt;/span&gt;

&lt;span class="c"&gt;# Install Hugo&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apk add &lt;span class="nt"&gt;--update&lt;/span&gt; wget ca-certificates &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  wget https://github.com/spf13/hugo/releases/download/v&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_BINARY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;tar &lt;/span&gt;xzf &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_BINARY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_BINARY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mv &lt;/span&gt;hugo /usr/bin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apk del wget ca-certificates &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; /var/cache/apk/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./ /site&lt;/span&gt;

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

&lt;span class="k"&gt;RUN &lt;/span&gt;/usr/bin/hugo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first part &lt;code&gt;FROM alpine:3.5 as build&lt;/code&gt; it's labeling that build step as &lt;code&gt;build&lt;/code&gt; so that in the next step we can get artifacts from this previous image, the artifact we'll be keeping is the built site.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HUGO_VERSION 0.41&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HUGO_BINARY hugo_${HUGO_VERSION}_Linux-64bit.tar.gz&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pretty self-explanatory, declare the Hugo version and use it to get the binary name that we're gonna get from the official GitHub releases&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apk add &lt;span class="nt"&gt;--update&lt;/span&gt; wget ca-certificates &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  wget https://github.com/spf13/hugo/releases/download/v&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_BINARY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;tar &lt;/span&gt;xzf &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_BINARY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_BINARY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mv &lt;/span&gt;hugo /usr/bin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apk del wget ca-certificates &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; /var/cache/apk/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;set -x&lt;/code&gt; will give us a nice output for these commands, then we are updating the repos and installing &lt;code&gt;wget&lt;/code&gt; and &lt;code&gt;ca-certificates&lt;/code&gt; from the alpine repositories, after that we're building the release binary download URL from the GitHub releases page and downloading it using wget, then we're uncompressing the tar file, deleting it and moving the binary to the /usr/bin folder, after that we do some standard clean-up tasks to get the final image to the lower size we can without much effort.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./ /site&lt;/span&gt;

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

&lt;span class="k"&gt;RUN &lt;/span&gt;/usr/bin/hugo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're copying the current directory (should be the Hugo site root folder) and moving it into /site (inside the container), next we're basically doing a change-dir to &lt;code&gt;/site&lt;/code&gt; and the last step is the one that actually runs the Hugo binary against /site and that gets us the final &lt;code&gt;/public&lt;/code&gt; folder with the assets that we're gonna server over Nginx.&lt;/p&gt;

&lt;h1&gt;
  
  
  Nginx Build Step
&lt;/h1&gt;

&lt;p&gt;This one is pretty simple and it's the second and last step, we're gonna be using the official Alpine Nginx repo to get a container with the &lt;a href="https://en.wikipedia.org/wiki/Reverse_proxy" rel="noopener noreferrer"&gt;reverse-proxy&lt;/a&gt; installed&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; maintainer Eduardo Reyes &amp;lt;eduardo@reyes.im&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./conf/default.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /site/public /var/www/site&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www/site&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is really simple, we're getting the official &lt;code&gt;nginx:alpine&lt;/code&gt; image and using it as our second build step, then I'm adding my maintainer label to the final container, copying over a Nginx configuration file over the container, the important step is this one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /site/public /var/www/site&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step is getting the public folder (the artifact we built on the first step) &lt;code&gt;--from&lt;/code&gt; the &lt;code&gt;build&lt;/code&gt; step of the multi-stage build and copying it over &lt;code&gt;/var/www/site&lt;/code&gt; were Nginx will serve it as static content thanks to the basic configuration file copied earlier and then we're changing dirs into the site, so that if we want to go into the container we're already where the code is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Dockerfile
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:3.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HUGO_VERSION 0.41&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HUGO_BINARY hugo_${HUGO_VERSION}_Linux-64bit.tar.gz&lt;/span&gt;

&lt;span class="c"&gt;# Install Hugo&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apk add &lt;span class="nt"&gt;--update&lt;/span&gt; wget ca-certificates &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  wget https://github.com/spf13/hugo/releases/download/v&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_BINARY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;tar &lt;/span&gt;xzf &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_BINARY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HUGO_BINARY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mv &lt;/span&gt;hugo /usr/bin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apk del wget ca-certificates &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; /var/cache/apk/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./ /site&lt;/span&gt;

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

&lt;span class="k"&gt;RUN &lt;/span&gt;/usr/bin/hugo

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;

&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; maintainer Eduardo Reyes &amp;lt;eduardo@reyes.im&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./conf/default.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /site/public /var/www/site&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www/site&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Building the image
&lt;/h1&gt;

&lt;p&gt;This one is a really simple docker command, you should run it from the root of your Hugo site.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker build -t some-hugo-site .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and to see the results just do:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run -d -p 8080:80 some-hugo-site&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I recommend using &lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;traefik&lt;/a&gt; to expose the container to the web, take advantage of the automatic let's encrypt integration and add a &lt;strong&gt;http -&amp;gt; https&lt;/strong&gt; redirect to get a nice TLS workflow.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>hugo</category>
    </item>
  </channel>
</rss>
