<?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: Edgar Roman</title>
    <description>The latest articles on DEV Community by Edgar Roman (@edgarroman).</description>
    <link>https://dev.to/edgarroman</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%2F918075%2F626bfa4b-13fe-4cc9-85af-58042f1c2bbd.png</url>
      <title>DEV Community: Edgar Roman</title>
      <link>https://dev.to/edgarroman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/edgarroman"/>
    <language>en</language>
    <item>
      <title>Workflow for transcribing audio in AWS</title>
      <dc:creator>Edgar Roman</dc:creator>
      <pubDate>Mon, 27 Feb 2023 21:16:55 +0000</pubDate>
      <link>https://dev.to/edgarroman/workflow-for-transcribing-audio-in-aws-2h1a</link>
      <guid>https://dev.to/edgarroman/workflow-for-transcribing-audio-in-aws-2h1a</guid>
      <description>&lt;p&gt;My sister had an interesting challenge for me recently. She had some audio that needed to be automatically run through &lt;a href="https://aws.amazon.com/transcribe/" rel="noopener noreferrer"&gt;AWS Transcribe&lt;/a&gt; from files uploaded to S3.&lt;/p&gt;

&lt;p&gt;Initially I thought that AWS Step Functions would be great, but opted for a simpler solution using direct events. Normally a workflow tool would be more flexible for more complex solutions and hard coding event driven events tend to make solutions brittle when future changes come, but often simple is better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Here's a high level overview of the flow:&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%2Ftpr7gj8308sx0tmv1cxi.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%2Ftpr7gj8308sx0tmv1cxi.png" alt="Architecture Diagram" width="476" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can describe the flow thus:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User uploads audio file (in mp3 format) to the &lt;code&gt;uploads/&lt;/code&gt; folder in the bucket&lt;/li&gt;
&lt;li&gt;Event Bridge detects the created object and triggers the &lt;code&gt;Start Transcribe Job&lt;/code&gt; lambda function&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Start Transcribe Job&lt;/code&gt; lambda initiates the AWS Transcribe Job and exits&lt;/li&gt;
&lt;li&gt;AWS Transcribe completes the conversion from audio to text and writes the file back to the S3 bucket in the &lt;code&gt;output/&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;Event Bridge detects a state change in the AWS Transcribe job and triggers the &lt;code&gt;Cleanup&lt;/code&gt; lambda function&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Guide
&lt;/h2&gt;

&lt;p&gt;Let's get into the how to build this system using the AWS console. Once you are familiar with the important steps, you may automate this process using an automated tool like &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the bucket
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Load up the AWS console for S3: &lt;a href="https://s3.console.aws.amazon.com/s3/buckets" rel="noopener noreferrer"&gt;https://s3.console.aws.amazon.com/s3/buckets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click on the "Create Bucket" button&lt;/li&gt;
&lt;li&gt;Enter the bucket name and you can accept all default options. In this example I put in:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;transcribe-workflow-experiment&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Once created, if you click on the bucket you should see a button for "Create Folder". Use this button to create:

&lt;ul&gt;
&lt;li&gt;A folder named &lt;code&gt;uploads&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A folder named &lt;code&gt;output&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Also on the properties page of the S3 bucket be sure to enable AWS Event Bridge or you won't get any events!&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create your "Start Transcribe Job" Lambda
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Load up the lambda part of the AWS Console: &lt;a href="https://us-east-1.console.aws.amazon.com/lambda/home" rel="noopener noreferrer"&gt;https://us-east-1.console.aws.amazon.com/lambda/home&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click on "Create Function"&lt;/li&gt;
&lt;li&gt;Use the following settings:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; transcribe-workflow-start-transcribe&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RunTime:&lt;/strong&gt; Python 3.9&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; x86_64&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;And accept all other default parameters by clicking 'Create Function'&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Enhance the Lambda IAM role
&lt;/h3&gt;

&lt;p&gt;When creating a new Lambda function, the default AWS settings will automatically create an IAM role associated with the function. This IAM roll will have the bare minimum permissions to execute the Lambda function and almost nothing else. We will need to add additional permissions to the IAM role in order to complete this overall architecture.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Once the Lambda function is created, click to see the details.&lt;/li&gt;
&lt;li&gt; Click on 'Configuration'&lt;/li&gt;
&lt;li&gt; Then click on the 'Role Name' and it should open a new tab&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You should now see the IAM permissions for the role associated with your Lambda function. We need to add the ability to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Access the S3 bucket we created previously&lt;/li&gt;
&lt;li&gt;  Start AWS Transcribe jobs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;First, we add S3 access. Click on the 'Add Permissions' dropdown and select 'Create Inline Policy'. Click on the 'JSON' tab and input the following policy and click 'Review Policy'. Make sure your bucket name you added replaces &lt;code&gt;transcribe-workflow-experiment&lt;/code&gt; in the JSON.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ListObjectsInBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::transcribe-workflow-experiment"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllObjectActions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:*Object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::transcribe-workflow-experiment/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;On the next screen, name like policy something descriptive like: "allow-bucket-access-for-transcribe-workflow".&lt;/li&gt;
&lt;li&gt;Now we add permission to start AWS Transcribe jobs. Repeat the same steps with a new policy and this JSON:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowTranscribeJobs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"transcribe:StartTranscriptionJob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add the Lambda code
&lt;/h3&gt;

&lt;p&gt;Next, we populate the python code that runs the Lambda function. You can see the code below is pretty straightforward, but there a couple things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The Lambda function does not actually interact directly with S3. So why did we add the IAM policy for S3 access? Because the AWS Transcribe job will use the same IAM role of the Lambda function that called it by default.&lt;/li&gt;
&lt;li&gt;  The function instructs AWS Transcribe to output the results back to the same bucket, but to a different folder. See the parameters &lt;code&gt;OutputBucketName&lt;/code&gt; and &lt;code&gt;OutputKey&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  The output file will be named after the &lt;code&gt;TranscriptionJobName&lt;/code&gt;. So we add a little utility function to 'slugify' the filename. To wit: "Audio File #3.mp3" becomes "audio-file-3-mp3".&lt;/li&gt;
&lt;li&gt;  Additional Transcribe options can be added as you see fit. E.g. Leveraging a custom dictionary.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;

&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;transcribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcribe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcribe-workflow-start-job started...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# The job name is important because the output file will be named after
&lt;/span&gt;        &lt;span class="c1"&gt;# the original audio file name, but 'slugified'.  This means it contains
&lt;/span&gt;        &lt;span class="c1"&gt;# only lower case characters, numbers, and hyphens
&lt;/span&gt;        &lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;slugify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcribe-workflow-start-job: received the following file &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;s3_uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# Kick off the transcription job
&lt;/span&gt;        &lt;span class="n"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_transcription_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;TranscriptionJobName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Media&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MediaFileUri&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s3_uri&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;LanguageCode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en-US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;OutputBucketName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;OutputKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lambda: Start Transcribe Complete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;# Small slugify code taken from:
# https://gist.github.com/gergelypolonkai/1866fd363f75f4da5f86103952e387f6
# Converts "Audio File #3.mp3" to "audio-file-3-mp3"
#
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;

&lt;span class="n"&gt;_punctuation_re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[\t !&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#$%&amp;amp;\'()*\-/&amp;lt;=&amp;gt;?@\[\\\]^_`{|},.]+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;slugify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Generate an ASCII-only slug.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_punctuation_re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NFKD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ascii&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;delim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create the Cleanup Lambda function
&lt;/h3&gt;

&lt;p&gt;Once the transcribe job is complete, we want this function to clean up a bit. Tasks needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Move the original audio file to a &lt;code&gt;completed&lt;/code&gt; folder or &lt;code&gt;failed&lt;/code&gt; folder depending on the job outcome&lt;/li&gt;
&lt;li&gt;  (Optional) Delete the transcription job. Even though AWS Transcribe automatically deletes jobs after 90 days, it could reduce clutter in the AWS console&lt;/li&gt;
&lt;li&gt;  (Optional) Contact a user with a notification that a job has completed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this blog post, we will only manage the original audio file. We primarily wanted to highlight the capability of a post-processing Lambda function.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load up the Lambda part of the AWS Console: &lt;a href="https://us-east-1.console.aws.amazon.com/lambda/home" rel="noopener noreferrer"&gt;https://us-east-1.console.aws.amazon.com/lambda/home&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click on "Create Function"&lt;/li&gt;
&lt;li&gt;Use the following settings:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; transcribe-workflow-transcribe-completed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RunTime:&lt;/strong&gt; Python 3.9&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; x86_64&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;And accept all other default parameters by clicking 'Create Function'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once complete, insert the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;

&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;transcribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcribe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcribe-workflow-cleanup started...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TranscriptionJobName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Transcribe job: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_transcription_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscriptionJobName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Job not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# Now extract the name of the original file
&lt;/span&gt;        &lt;span class="c1"&gt;# in the form:
&lt;/span&gt;        &lt;span class="c1"&gt;# s3://transcribe-workflow-experiment/uploads/Sample Audio.mp3
&lt;/span&gt;        &lt;span class="n"&gt;original_audio_s3_uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TranscriptionJob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MediaFileUri&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# Use [5:] to remove the 's3://' and partition returns the string split by the first '/'
&lt;/span&gt;        &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original_audio_s3_uri&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:].&lt;/span&gt;&lt;span class="nf"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;media_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Media file &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;media_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is at: key &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; in bucket &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Job status will either be "COMPLETED" or "FAILED"
&lt;/span&gt;        &lt;span class="n"&gt;job_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TranscriptionJobStatus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;new_folder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completed/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;job_status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;new_folder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# Tell AWS to 'move' the file in S3 which really means making a copy and deleting the original
&lt;/span&gt;        &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CopySource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new_folder&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;media_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lambda: Transcribe Cleanup Complete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enhance the Lambda IAM role for 2nd Lambda function
&lt;/h3&gt;

&lt;p&gt;We will need to add additional permissions to the IAM role in order to complete this overall architecture, specifically the ability to access the S3 bucket we created previously.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Once the Lambda function is created, click to see the details.&lt;/li&gt;
&lt;li&gt; Click on 'Configuration'&lt;/li&gt;
&lt;li&gt; Then click on the 'Role Name' and it should open a new tab&lt;/li&gt;
&lt;li&gt; You should now see the IAM permissions for the role associated with your Lambda function.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on the 'Add Permissions' dropdown and select 'Create Inline Policy'. Click on the 'JSON' tab and input the following policy and click 'Review Policy'. Make sure your bucket name you added replaces &lt;code&gt;transcribe-workflow-experiment&lt;/code&gt; in the JSON.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ListObjectsInBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::transcribe-workflow-experiment"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllObjectActions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:*Object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::transcribe-workflow-experiment/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the next screen, name like policy something descriptive like: "allow-bucket-access-for-transcribe-workflow-cleanup".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now we add permission to query AWS Transcribe jobs. Repeat the same steps with a new policy and this JSON:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"QueryTranscriptionJobs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"transcribe:GetTranscriptionJob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wire up events with EventBridge
&lt;/h3&gt;

&lt;p&gt;Now we need to connect all these elements together. We do this with AWS EventBridge. EventBridge is kind of a universal event bus that can tap into many events throughout the AWS ecosystem. For example, the creation of an object in S3 can directly trigger a Lambda function. However, only EventBridge can detect the AWS Transcribe job status change. So to be consistent, we will use EventBridge for both S3 object creation and AWS Transcribe job status change.&lt;/p&gt;

&lt;h4&gt;
  
  
  EventBridge setup for new S3 object
&lt;/h4&gt;

&lt;p&gt;In this setup, we will indicate to EventBridge to process all 'Object Created' events in the bucket 'transcribe-workflow-experiment' in the folder 'uploads/'. More information about EventBridge patterns can be found in the &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html" rel="noopener noreferrer"&gt;AWS Docs&lt;/a&gt;. Any other objects created in other folders in the S3 bucket will not be processed by this rule.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the EventBridge page in the AWS console: &lt;a href="https://us-east-1.console.aws.amazon.com/events/home" rel="noopener noreferrer"&gt;https://us-east-1.console.aws.amazon.com/events/home&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;From the left menu, select 'Rules'&lt;/li&gt;
&lt;li&gt;Click on the 'Create Rule' button&lt;/li&gt;
&lt;li&gt;Use the following parameters and click 'Next'

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; transcribe-workflow-new-object-rule&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description:&lt;/strong&gt; Notifies Lambda there is a new audio file in S3 in the '/uploads' folder&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Bus:&lt;/strong&gt; default&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the 'Built Event Pattern' page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event source:&lt;/strong&gt; AWS events or EventBridge partner events&lt;/li&gt;
&lt;li&gt;Scroll past the Sample Event section&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creation method:&lt;/strong&gt; Select 'Custom Pattern (JSON Editor)'&lt;/li&gt;
&lt;li&gt;Insert the following and make sure you update the bucket name to your bucket, then click 'Next'
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws.s3"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"detail-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Object Created"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"transcribe-workflow-experiment"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uploads"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the 'Select target(s)` screen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Target 1:&lt;/strong&gt; Select AWS Service&lt;/li&gt;
&lt;li&gt;In the dropdown, use target type 'Lambda Function'&lt;/li&gt;
&lt;li&gt;Select the first Lambda function that starts the transcribe job&lt;/li&gt;
&lt;li&gt;Select 'Next'&lt;/li&gt;
&lt;li&gt;You may skip tags and click 'Next' and then complete the process&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  EventBridge setup for AWS Transcribe Job Complete
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;From the left menu, select 'Rules'&lt;/li&gt;
&lt;li&gt;Click on the 'Create Rule' button&lt;/li&gt;
&lt;li&gt;Use the following parameters and click 'Next'

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; transcribe-workflow-job-complete-rule&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description:&lt;/strong&gt; Notifies Lambda the transcribe job is complete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Bus:&lt;/strong&gt; default&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the 'Built Event Pattern' page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event source:&lt;/strong&gt; AWS events or EventBridge partner events&lt;/li&gt;
&lt;li&gt;Scroll past the Sample Event section&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creation method:&lt;/strong&gt; Select 'Custom Pattern (JSON Editor)'&lt;/li&gt;
&lt;li&gt;Insert the following:
&lt;code&gt;&lt;/code&gt;&lt;code&gt;json
{
"source": ["aws.transcribe"],
"detail-type": ["Transcribe Job State Change"],
"detail": {
    "TranscriptionJobStatus": ["COMPLETED", "FAILED"]
}
}
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click 'Next'&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the 'Select target(s)` screen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Target 1:&lt;/strong&gt; Select AWS Service&lt;/li&gt;
&lt;li&gt;In the dropdown, use target type 'Lambda Function'&lt;/li&gt;
&lt;li&gt;Select the second Lambda function that cleans up the transcribe job&lt;/li&gt;
&lt;li&gt;Select 'Next'&lt;/li&gt;
&lt;li&gt;You may skip tags and click 'Next' and then complete the process&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Test it out
&lt;/h3&gt;

&lt;p&gt;You can try this out by using the AWS Console and uploading files into the 'uploads/' folder of the bucket. Try loading an audio file and seeing the output. Next, try loading a non-audio file like a jpg and watch the file get swept into the 'failed' folder.&lt;/p&gt;

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

&lt;p&gt;As you can see, this is essentially a two step workflow. More tasks can be added to the 2nd Lambda function if you wish. To further extend this, you can insert Step Functions to have a more flexible workflow. Most of the code will be the same with some slight tweaks to the event formats.&lt;/p&gt;

&lt;p&gt;One difficulty you will have with Step Functions is to pause the flow while AWS Transcribe is processing the job. Step Functions generates a token that is needed to resume the workflow and that token will not be stored by AWS Transcribe. So you'd need a mechanism to either store the token in something like DynamoDB or do some deep inspection of the workflow to extract the resume token.&lt;/p&gt;

&lt;p&gt;Regardless, I hope this helps someone working on a similar effort.&lt;/p&gt;

&lt;p&gt;Cover Photo by &lt;a href="https://unsplash.com/@mgmaasen?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Michael Maasen&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/ZY7fuakuXJ0?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Opinionated Docker development workflow for Node.js projects - Part 2</title>
      <dc:creator>Edgar Roman</dc:creator>
      <pubDate>Wed, 31 Aug 2022 20:15:00 +0000</pubDate>
      <link>https://dev.to/edgarroman/opinionated-docker-development-workflow-for-nodejs-projects-part-2-21ao</link>
      <guid>https://dev.to/edgarroman/opinionated-docker-development-workflow-for-nodejs-projects-part-2-21ao</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dev.to/edgarroman/why-use-docker-in-2022-1mjb/"&gt;recent post&lt;/a&gt;, I described &lt;strong&gt;why&lt;/strong&gt; you'd want to use Docker to develop server applications. Then I described how to use an &lt;a href="https://dev.to/edgarroman/opinionated-docker-development-workflow-for-nodejs-projects-part-1-26c0"&gt;opinionated workflow&lt;/a&gt;. In this post, I'll describe how the workflow operates under the covers.&lt;/p&gt;

&lt;p&gt;If you haven't read &lt;a href="https://dev.to/edgarroman/opinionated-docker-development-workflow-for-nodejs-projects-part-1-26c0"&gt;part 1&lt;/a&gt;, I strongly suggest you check it out now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Directory Structure and Files
&lt;/h2&gt;

&lt;p&gt;Remember the directory structure? We established a clear directory structure that isolates all your application specific code into a single sub-folder and the top level directory holds all the workflow files.&lt;/p&gt;

&lt;p&gt;It looks 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;└── Main_Project_Directory/
    ├── server-code/
    │   ├── server.js
    │   ├── package.json
    │   └── ... (All your other source code files)
    ├── .gitignore
    ├── .dockerignore
    ├── Dockerfile
    ├── docker-compose.yml
    └── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Here's the complete &lt;code&gt;Dockerfile&lt;/code&gt;&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="c"&gt;# Base node images can be found here: https://hub.docker.com/_/node?tab=description&amp;amp;amp%3Bpage=1&amp;amp;amp%3Bname=alpine&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NODE_IMAGE=node:16.17-alpine&lt;/span&gt;

&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="c"&gt;# Base Image&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# All these commands are common to both development and production builds&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;$NODE_IMAGE&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;base&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NPM_VERSION=npm@8.18.0&lt;/span&gt;

&lt;span class="c"&gt;# While root is the default user to run as, why not be explicit?&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;

&lt;span class="c"&gt;# Run tini as the init process and it will clean zombie processes as needed&lt;/span&gt;
&lt;span class="c"&gt;# Generally you can achieve this same effect by adding `--init` in your `docker RUN` command&lt;/span&gt;
&lt;span class="c"&gt;# And Nodejs servers tend not to spawn processes, so this is belt and suspenders&lt;/span&gt;
&lt;span class="c"&gt;# More info: https://github.com/krallin/tini&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; tini
&lt;span class="c"&gt;# Tini is now available at /sbin/tini&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/sbin/tini", "--"]&lt;/span&gt;

&lt;span class="c"&gt;# Upgrade some global packages&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$NPM_VERSION&lt;/span&gt;

&lt;span class="c"&gt;# Specific to your framework&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Some frameworks force a global install tool such as aws-amplify or firebase.  Run those commands here&lt;/span&gt;
&lt;span class="c"&gt;# RUN npm install -g firebase&lt;/span&gt;

&lt;span class="c"&gt;# Create space for our code to live&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/node/app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; node:node /home/node/app
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/node/app&lt;/span&gt;

&lt;span class="c"&gt;# Switch to the `node` user instead of running as `root` for improved security&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;

&lt;span class="c"&gt;# Expose the port to listen on here.  Express uses 8080 by default so we'll set that here.&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=8080&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; $PORT&lt;/span&gt;

&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="c"&gt;# Development build&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# These commands are unique to the development builds&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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;development&lt;/span&gt;

&lt;span class="c"&gt;# Copy the package.json file over and run `npm install`&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; server-code/package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Now copy rest of the code.  We separate these copies so that Docker can cache the node_modules directory&lt;/span&gt;
&lt;span class="c"&gt;# So only when you add/remove/update package.json file will Docker rebuild the node_modules dir.&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; server-code ./&lt;/span&gt;

&lt;span class="c"&gt;# Finally, if the container is run in headless, non-interactive mode, start up node&lt;/span&gt;
&lt;span class="c"&gt;# This can be overridden by the user running the Docker CLI by specifying a different endpoint&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npx", "nodemon","server.js"]&lt;/span&gt;

&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="c"&gt;# Production build&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# These commands are unique to the production builds&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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;production&lt;/span&gt;

&lt;span class="c"&gt;# Indicate to all processes in the container that this is a production build&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=${NODE_ENV}&lt;/span&gt;

&lt;span class="c"&gt;# Now copy all source code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node server-code ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Finally, if the container is run in headless, non-interactive mode, start up node&lt;/span&gt;
&lt;span class="c"&gt;# This can be overridden by the user running the Docker CLI by specifying a different endpoint&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node","server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dockerfile: Version Management and Multi-Stage
&lt;/h3&gt;

&lt;p&gt;Let's start at the top of the file:&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="c"&gt;# Base node images can be found here: https://hub.docker.com/_/node?tab=description&amp;amp;amp%3Bpage=1&amp;amp;amp%3Bname=alpine&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NODE_IMAGE=node:16.17-alpine&lt;/span&gt;

&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="c"&gt;# Base Image&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# All these commands are common to both development and production builds&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;$NODE_IMAGE&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;base&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NPM_VERSION=npm@8.18.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we know that versions of Node.js and &lt;code&gt;npm&lt;/code&gt; change over time. These are critical dependencies so we put them up front at the top so developers can update them when starting a new project.&lt;/p&gt;

&lt;p&gt;Also note that this is a [multi-stage Dockerfile (&lt;a href="https://docs.docker.com/develop/develop-images/multistage-build/"&gt;https://docs.docker.com/develop/develop-images/multistage-build/&lt;/a&gt;). This is to accommodate a single file for both development and production builds. So we have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A common &lt;code&gt;base&lt;/code&gt; stage&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;development&lt;/code&gt; stage&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;production&lt;/code&gt; stage&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Dockerfile: Base stage
&lt;/h3&gt;

&lt;p&gt;These are the commands in the Dockerfile for the &lt;code&gt;base&lt;/code&gt; stage&lt;/p&gt;

&lt;p&gt;We explicitly set the user to be &lt;code&gt;root&lt;/code&gt; for the next several instructions&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="c"&gt;# While root is the default user to run as, why not be explicit?&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're adding a handler to capture termination signals from the system to gracefully shutdown. While not super important for Node.js containers, it's a good practice in case we want to use this Dockerfile for other languages such as Python. More details can be found at &lt;a href="https://github.com/krallin/tini"&gt;https://github.com/krallin/tini&lt;/a&gt;.&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="c"&gt;# Run tini as the init process and it will clean zombie processes as needed&lt;/span&gt;
&lt;span class="c"&gt;# Generally you can achieve this same effect by adding `--init` in your `docker RUN` command&lt;/span&gt;
&lt;span class="c"&gt;# And Nodejs servers tend not to spawn processes, so this is belt and suspenders&lt;/span&gt;
&lt;span class="c"&gt;# More info: https://github.com/krallin/tini&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; tini
&lt;span class="c"&gt;# Tini is now available at /sbin/tini&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/sbin/tini", "--"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We install the specified version of &lt;code&gt;npm&lt;/code&gt; here. We also have the opportunity to install other packages that are required to be global here just as &lt;code&gt;aws-amplify&lt;/code&gt; or &lt;code&gt;firebase&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
While I hope the trend of requiring global pacakages goes away, we can easily support it using Docker. Be sure to identify a specific version of your global package to prevent nasty surprises later!&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="c"&gt;# Upgrade some global packages&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$NPM_VERSION&lt;/span&gt;

&lt;span class="c"&gt;# Specific to your framework&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Some frameworks force a global install tool such as aws-amplify or firebase.  Run those commands here&lt;/span&gt;
&lt;span class="c"&gt;# RUN npm install -g firebase&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create an arbitrary location for the container to store your code. You can call this anything you'd like, but we're following the traditional Linux approach here for consistency. Note that we also have to change the ownership of this directory to the proper user (called &lt;code&gt;node&lt;/code&gt;) so that the user can properly create/read/write files in this directory.&lt;/p&gt;

&lt;p&gt;So your application will live in the directory &lt;code&gt;/home/node/app&lt;/code&gt;. Finally, switch subsequent build instructions to be executed by the &lt;code&gt;node&lt;/code&gt;. This follows the &lt;a href="https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#non-root-user"&gt;recommended best practices&lt;/a&gt;&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="c"&gt;# Create space for our code to live&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/node/app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; node:node /home/node/app
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/node/app&lt;/span&gt;

&lt;span class="c"&gt;# Switch to the `node` user instead of running as `root` for improved security&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expose the port on which the application will listen. This is configurable, but must be changed in several files.&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="c"&gt;# Expose the port to listen on here.  Express uses 8080 by default so we'll set that here.&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=8080&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; $PORT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dockerfile: Development stage
&lt;/h3&gt;

&lt;p&gt;These are the commands in the Dockerfile for the &lt;code&gt;development&lt;/code&gt; stage.  When doing a build, you must specify the stage using the flag: &lt;code&gt;--target=development&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First we copy over the &lt;code&gt;package.json&lt;/code&gt; file from the &lt;code&gt;server-code&lt;/code&gt; directory into the working directory &lt;code&gt;/home/node/app&lt;/code&gt; (set above by &lt;code&gt;WORKDIR&lt;/code&gt;). We also include any &lt;code&gt;package-lock.json&lt;/code&gt; files.&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="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="c"&gt;# Development build&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# These commands are unique to the development builds&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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;development&lt;/span&gt;

&lt;span class="c"&gt;# Copy the package.json file over and run `npm install`&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; server-code/package*.json ./&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we run &lt;code&gt;npm install&lt;/code&gt; to establish all the application dependencies including development dependencies.&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;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we copy the rest of the code. As the comment states, we separate this step for build optimization. If we don't modify the package requirements, then we won't have to rebuild the &lt;code&gt;npm install&lt;/code&gt; step which could take a long time. More often, we'll be changing just the application source code, so subsequent builds would be very fast.&lt;/p&gt;

&lt;p&gt;And finally, we run &lt;code&gt;npx nodemon server.js&lt;/code&gt; to start the application using &lt;code&gt;nodemon&lt;/code&gt; to reload the code when it changes.&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="c"&gt;# Now copy rest of the code.  We separate these copies so that Docker can cache the node_modules directory&lt;/span&gt;
&lt;span class="c"&gt;# So only when you add/remove/update package.json file will Docker rebuild the node_modules dir.&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; server-code ./&lt;/span&gt;

&lt;span class="c"&gt;# Finally, if the container is run in headless, non-interactive mode, start up node&lt;/span&gt;
&lt;span class="c"&gt;# This can be overridden by the user running the Docker CLI by specifying a different endpoint&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npx", "nodemon","server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dockerfile: Production stage
&lt;/h3&gt;

&lt;p&gt;These are the commands in the Dockerfile for the &lt;code&gt;development&lt;/code&gt; stage. When doing a build, you must specify the stage using the flag: &lt;code&gt;--target=development&lt;/code&gt;.&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="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="c"&gt;# Production build&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# These commands are unique to the production builds&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#####################################################################&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&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;production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set some environment variables so all downstream scripts can discover this is a production build and act accordingly.&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="c"&gt;# Indicate to all processes in the container that this is a production build&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=${NODE_ENV}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy over all the source code at once. We are going to do a full &lt;code&gt;npm install&lt;/code&gt; so there's no need to break up the multiple copy steps like we did in the development stage. After we do the install, we tell &lt;code&gt;npm&lt;/code&gt; to clear it's cache in an attempt to make the image size smaller.&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="c"&gt;# Now copy all source code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node server-code ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we kick off the application by telling &lt;code&gt;node&lt;/code&gt; to run the application &lt;code&gt;server.js&lt;/code&gt;.&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="c"&gt;# Finally, if the container is run in headless, non-interactive mode, start up node&lt;/span&gt;
&lt;span class="c"&gt;# This can be overridden by the user running the Docker CLI by specifying a different endpoint&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node","server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Development Mode without Docker Compose
&lt;/h2&gt;

&lt;p&gt;We'll show how to use this Dockerfile using just the command line interface (CLI). In the next section, we'll simplify this with Docker Compose.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;If this the first time you are running the container, or if you have changed &lt;em&gt;any&lt;/em&gt; package dependencies, then run:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; mynodeapp:DEV &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;development
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This will build the image using the development stage instructions from the Dockerfile. Note that you have to call it something, so we're using &lt;code&gt;mynodeapp&lt;/code&gt; with the version of &lt;code&gt;DEV&lt;/code&gt;. Using &lt;code&gt;DEV&lt;/code&gt; helps to avoid production deployment since it's not using semantic versioning.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To run the container, type the following command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/server-code:/home/node/app"&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; /home/node/app/node_modules mynodeapp:DEV
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What you should see is Docker will start to run your container in the terminal window and any console messages will appear as they are printed out.&lt;/p&gt;

&lt;p&gt;Changes to the source code should trigger a reload of Node and will be reflected in the console.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If you make any changes to dependent packages, then you'll have to run the &lt;code&gt;docker build&lt;/code&gt; command as shown above. Run the build any time you add, update, or remove a package.&lt;/li&gt;
&lt;li&gt;  We assume that node will be running on port 8080. If this is not the case for your project, feel free to change it, but make sure to change it everywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This workflow is made possible by some clever Docker commands. We'll expand on the command above here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;docker run&lt;/code&gt;: This is the primary Docker command to take a container image and run it&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;-ti&lt;/code&gt;: This instructs Docker to run this container interactively so you can see the output console&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;--rm&lt;/code&gt;: After you exit the container instance by pressing &lt;code&gt;Ctrl-c&lt;/code&gt; this flag instructs Docker to clean up after the container&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;-p 8080:8080&lt;/code&gt;: Ensure port 8080 on the container is mapped to port 8080 on your local machine so you can use &lt;code&gt;http://localhost:8080&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;-v "$(pwd)/server-code:/home/node/app"&lt;/code&gt;: This maps the directory &lt;code&gt;server-code&lt;/code&gt; (along with your source code) into the container directory &lt;code&gt;/home/node/app&lt;/code&gt;. So your source code and everything in the &lt;code&gt;server-code&lt;/code&gt; directory is available in the container.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;-v /home/node/app/node_modules&lt;/code&gt;: This is a special command that excludes the &lt;code&gt;node_modules&lt;/code&gt; directory on your local machine and instead keeps the container's &lt;code&gt;node_modules&lt;/code&gt; directory that was created during the build phase. This is important because the &lt;code&gt;node_modules&lt;/code&gt; on your local machine may be full of packages that are specific to the local machine operating system. And since we want the packages for the container, this flag makes that one directory take priority.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;mynodeapp:DEV&lt;/code&gt;: This is whatever you want to call your container image. We tag this image with &lt;code&gt;DEV&lt;/code&gt; to make sure you don't accidentaly deploy this version.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Development Mode simplified with Docker Compose
&lt;/h2&gt;

&lt;p&gt;The commands to enable this workflow are very long, complex, and difficult to remember. To simplify this workflow, we have a few options. One common option is create shell scripts for the commands above. This works, but will be operating system specific (e.g. Windows will need a different solution than most shells).&lt;/p&gt;

&lt;p&gt;We can use Docker Compose to simplify our workflow. Docker Compose is a tool that allows you to put all the commands into a file that does the work for you. It also allows you to spin up multiple containers at the same time. This is useful if you have to spin up both a web server and a database server for local development. That scenario is outside the scope of this post.&lt;/p&gt;

&lt;p&gt;Note that the Docker Compose we are going to show here is for development flow only.&lt;/p&gt;

&lt;p&gt;First, create the &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  app:
    build:
      context: .
      target: development
      args:
        - NODE_ENV=development
    environment:
        - NODE_ENV=development
    ports:
      - "8080:8080"
    volumes:
      - ./server-code:/home/node/app
      - /home/node/app/node_modules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we've put the same command line arguments shown earlier into to the file itself. So that makes it easy to build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;strong&gt;really&lt;/strong&gt; easy to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when you're finished&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;And that's about it. Don't forget the &lt;code&gt;.dockerignore&lt;/code&gt; and optionally the &lt;code&gt;.gitignore&lt;/code&gt; but you can customize those as you see fit.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>docker</category>
      <category>node</category>
      <category>containerapps</category>
    </item>
    <item>
      <title>Opinionated Docker development workflow for Node.js projects - Part 1</title>
      <dc:creator>Edgar Roman</dc:creator>
      <pubDate>Wed, 31 Aug 2022 19:24:59 +0000</pubDate>
      <link>https://dev.to/edgarroman/opinionated-docker-development-workflow-for-nodejs-projects-part-1-26c0</link>
      <guid>https://dev.to/edgarroman/opinionated-docker-development-workflow-for-nodejs-projects-part-1-26c0</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dev.to/edgarroman/why-use-docker-in-2022-1mjb/"&gt;recent post&lt;/a&gt;, I described &lt;strong&gt;why&lt;/strong&gt; you'd want to use Docker to develop server applications. In this post, I'll describe &lt;strong&gt;how&lt;/strong&gt; to develop a Node.js application with Docker.&lt;/p&gt;

&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;The goals we'd like to accomplish in this blog post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  We are focusing on Node.js environment to write a server side application (e.g. Express, Sails, or other)&lt;/li&gt;
&lt;li&gt;  Allow local development without Docker (optional)&lt;/li&gt;
&lt;li&gt;  Allow local development with Docker with hot refresh when code changes&lt;/li&gt;
&lt;li&gt;  Provide instructions to build images for testing and production&lt;/li&gt;
&lt;li&gt;  Isolate container scripts from source code so one folder structure can be used for many projects&lt;/li&gt;
&lt;li&gt;  Be straightforward, but explain all the steps so modifications and updates can be made&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is divided into two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to use the workflow (this post!)&lt;/li&gt;
&lt;li&gt;Dive into the details of how the Dockerfiles work&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll start with how to use the workflow and readers can continue on to the working details if interested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Docker Terminology
&lt;/h2&gt;

&lt;p&gt;For those who are new to Docker, I use some terms in this post and wanted to quickly define them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dockerfile:&lt;/strong&gt; A file that describes a set of instructions to Docker Desktop to build an image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image:&lt;/strong&gt; A file that contains the end results of instructions of a Dockerfile after Docker Desktop performs&lt;br&gt;
a build&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container:&lt;/strong&gt; A running instance of your image that can execute code&lt;/p&gt;

&lt;p&gt;That should be enough to get you rolling - let's get to the workflow!&lt;/p&gt;
&lt;h1&gt;
  
  
  How to use the workflow
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Directory Structure and Files
&lt;/h2&gt;

&lt;p&gt;We establish a clear directory structure that isolates all your application specific code into a single sub-folder and the top level directory holds all the workflow files.&lt;/p&gt;

&lt;p&gt;It looks 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;└── Main_Project_Directory/
    ├── server-code/
    │   ├── server.js
    │   ├── package.json
    │   └── ... (All your other source code files)
    ├── .gitignore
    ├── .dockerignore
    ├── Dockerfile
    ├── docker-compose.yml
    └── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This graph generated on &lt;a href="https://tree.nathanfriend.io/"&gt;https://tree.nathanfriend.io/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Notes&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;server-code&lt;/code&gt; directory is an arbitrary name.&lt;br&gt;
You may rename it, but be sure to update all the references in the Dockerfiles and Docker commands shown in this blog post. The purpose of this subdirectory is to isolate your server code from all the workflow stuff.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;docker-compose.yml&lt;/code&gt;, and &lt;code&gt;.dockerignore&lt;/code&gt; files will be taken from &lt;a href="https://github.com/edgarroman/docker-setup-node-container"&gt;this repo&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your application must start with the file &lt;code&gt;server.js&lt;/code&gt; because the container will run &lt;code&gt;node server.js&lt;/code&gt; when launching. If you want to rename this, then you will have to update the files to refer to your own start file.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;You'll need to install &lt;a href="https://www.docker.com/products/docker-desktop/"&gt;Docker Desktop&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Clone this repo: &lt;a href="https://github.com/edgarroman/docker-setup-node-container"&gt;https://github.com/edgarroman/docker-setup-node-container&lt;/a&gt; or just take the Docker related files and build a directory structure as shown above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updates
&lt;/h3&gt;

&lt;p&gt;As time goes on, you'll want to modify / upgrade the versions of Node.js and &lt;code&gt;npm&lt;/code&gt;. You can find the versions at the top of the &lt;code&gt;Dockerfile&lt;/code&gt;. At the time of this writing the lines look like:&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="c"&gt;# Base node images can be found here: https://hub.docker.com/_/node?tab=description&amp;amp;amp%3Bpage=1&amp;amp;amp%3Bname=alpine&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NODE_IMAGE=node:16.17-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the version of Node.js, head to &lt;a href="https://hub.docker.com/_/node?tab=description&amp;amp;amp%3Bpage=1&amp;amp;amp%3Bname=alpine"&gt;the official node docker hub&lt;/a&gt; and pick your base docker image. I recommend you stick with &lt;strong&gt;alpine&lt;/strong&gt; unless you have additional needs. Replace the 2nd line in the &lt;code&gt;Dockerfile&lt;/code&gt; with your desired tag.&lt;/p&gt;

&lt;p&gt;For the npm verison, see line 11. Update this as you see fit.&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;ARG&lt;/span&gt;&lt;span class="s"&gt; NPM_VERSION=npm@8.18.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All other versions of packages and whatnot are up to your preferences inside your app.&lt;/p&gt;

&lt;h1&gt;
  
  
  Workflow Guide
&lt;/h1&gt;

&lt;p&gt;We'll explore workflows of developing and testing your code. There are a number of workflows that we'll talk about in this post.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Local development without containers&lt;/li&gt;
&lt;li&gt;Local development with containers (Preferred)&lt;/li&gt;
&lt;li&gt;Production Build and Local Testing with containers&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Local development without containers
&lt;/h2&gt;

&lt;p&gt;This optional workflow does not use Docker at all.&lt;/p&gt;

&lt;p&gt;Using this workflow allows you to develop your code locally on your system with the least number of abstractions and complications. But it also means you have to install the correct version of Node.js and npm locally.&lt;/p&gt;

&lt;p&gt;Your local system will be directly running Node and directly loading your code. This workflow requires the least amount of processing power by your machine and will provide the most responsive development environment. When you make changes to your code, they be reflected as quickly as possible. (using &lt;code&gt;nodemon&lt;/code&gt; to hot reload your code when changes are detected)&lt;/p&gt;

&lt;p&gt;The downside to this approach is that most likely your local machine is not running the operating system that your final container will be running. If you're running Windows, MacOS, or even some flavors of Linux, the packages used locally may not be identical to those ultimately used in production.&lt;/p&gt;

&lt;p&gt;The differences these packages have between platforms could inject subtle bugs and errors that would be confounding and difficult to debug. While many straightforward Javascript packages may be identical between platforms, there may be differences when your code needs to interact with the host machine's operating system.&lt;/p&gt;

&lt;p&gt;With the pitfalls noted above, why should you take this approach? I would only recommend this approach if you are working in an environment where running Docker Desktop puts too much stress on your machine.&lt;/p&gt;

&lt;p&gt;In general, I suggest using the next workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local development with containers (Preferred)
&lt;/h2&gt;

&lt;p&gt;This workflow allows you to develop by running your code in a container environment. This container environment matches exactly what you will be deploying to production. And you don't need to install anything on your local machine aside from Docker Desktop.&lt;/p&gt;

&lt;p&gt;In addition, if you are working with a team, then you can be assured that regardless of operating system they are running, the code will behave the same across all hosts.&lt;/p&gt;

&lt;p&gt;A key benefit of this workflow is that you can edit your source code and any updates will be reflected in the container. We are still using &lt;code&gt;nodemon&lt;/code&gt; to detect source code changes and reload Node. This greatly eases development by allowing developers to see changes much faster than having to rebuild the image on every change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to get up and running
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Start Docker Desktop on your local machine&lt;/li&gt;
&lt;li&gt; Navigate to the main project directory (not in &lt;code&gt;server-code&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If this the first time you are running this workflow, or if you have changed &lt;em&gt;any&lt;/em&gt; package dependencies, then run:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose build
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This step will run &lt;code&gt;npm install&lt;/code&gt; in your image and lock in whatever you list in &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now run the following command to create a container (running instance of your image)&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You'll be able to see your project running at &lt;a href="http://localhost:8080/"&gt;http://localhost:8080/&lt;/a&gt;. And you'll be able to see any logs printed out to the console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Press Control-C to exit the console and stop the container. (Equivalent to &lt;code&gt;docker compose stop&lt;/code&gt; if you're familiar with Docker commands)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At this point your container is stopped, but Docker has it ready to start up again just in case. If you're finished developing or you need to make package changes, type the following to have Docker Desktop do a complete cleanup. It will remove the container, but keep your image around in case you want to start it again.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If you make any changes to dependent packages, then you'll have to run the &lt;code&gt;docker compose build&lt;/code&gt; command as shown above. &lt;strong&gt;Do this anytime you add, update, or remove a package&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  We assume that node will be running on port 8080. If this is not the case for your project, feel free to change it, but make sure to change it everywhere, especially &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;docker-compose.yml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  You may find an empty &lt;code&gt;node_modules&lt;/code&gt; under the &lt;code&gt;server-code&lt;/code&gt; directory. That's ok. When you do a build, the &lt;code&gt;node_modules&lt;/code&gt; directory is created inside your Docker image, but not pulled from your local machine. So an empty &lt;code&gt;node_modules&lt;/code&gt; is normal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production Build and Local Testing with containers
&lt;/h2&gt;

&lt;p&gt;This workflow allows you to test your container by running it locally but with production settings. It's an exact match of what you would deploy in production, but it allows you to view the console output to help remove any bugs or errors.&lt;/p&gt;

&lt;p&gt;For this workflow, there is no live reloading of source code. So if you make a change to the source code, you'll have to run the build step for every change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to get up and running
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Start Docker Desktop on your local machine&lt;/li&gt;
&lt;li&gt;Navigate to the main project directory (not in &lt;code&gt;server-code&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To build the image:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="nt"&gt;-t&lt;/span&gt; mynodeapp:1.00
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The &lt;code&gt;--target=production&lt;/code&gt; is a very important flag here. It indicates to the build process to strip out any development dependencies and extraneous files.&lt;/p&gt;

&lt;p&gt;A note here on the name of your image. I've picked &lt;code&gt;mynodeapp&lt;/code&gt; as the name and the version as &lt;code&gt;1.00&lt;/code&gt;. I suggest you call your application something that is meaningful to you and follow &lt;a href="https://semver.org/"&gt;semantic versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To run an instance of your production image locally, run the following command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 mynodeapp:1.00
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Another note here is that you're running the production version of your application, but most non-trivial apps will need connectivity to other services such as a database. It's left as an exercise for the reader to provide such connectivity.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Production Deployment
&lt;/h3&gt;

&lt;p&gt;Deploying your production image is outside the scope of this blog post. Especially since it varies wildly based on your Docker hosting environment.&lt;/p&gt;

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

&lt;p&gt;This is end of the first part of this post where we explained how to use this workflow. Part two will dive into the details of how the Dockerfiles were created and how they enable the workflow.&lt;/p&gt;

&lt;p&gt;Here's a link to part 2: &lt;a href="https://dev.to/edgarroman/opinionated-docker-development-workflow-for-nodejs-projects-part-2-21ao"&gt;https://dev.to/edgarroman/opinionated-docker-development-workflow-for-nodejs-projects-part-2-21ao&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that you'll probably want to read part 2 when you decide you want to make modifications to the workflow illustrated here.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>docker</category>
      <category>node</category>
      <category>containerapps</category>
    </item>
    <item>
      <title>Why use Docker in 2022?</title>
      <dc:creator>Edgar Roman</dc:creator>
      <pubDate>Wed, 31 Aug 2022 19:00:03 +0000</pubDate>
      <link>https://dev.to/edgarroman/why-use-docker-in-2022-1mjb</link>
      <guid>https://dev.to/edgarroman/why-use-docker-in-2022-1mjb</guid>
      <description>&lt;p&gt;When Docker first became popular a few years ago, I generally dismissed it as a fad: Something that the hipsters were adopting just so they could be cool.&lt;br&gt;&lt;br&gt;
I mean, why add this extra layer on top of everything and add complexity to your development workflow? And any benefit you may glean would be minimal.&lt;/p&gt;

&lt;p&gt;I didn't dive right into containers, but now things have changed. Over the past few years, cloud providers have rolled out some fantastic offerings that make me want to use Docker. Here are the reasons why using Docker containers in your developer workflow make a lot of sense in 2022.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stable development environments
&lt;/h2&gt;

&lt;p&gt;Gone are the days when you have a single development environment that all your projects leverage. If you start a handful of projects every year, then keeping those projects up to date could take a huge amount of time. In recent history, &lt;a href="https://en.wikipedia.org/wiki/History_of_Python#Table_of_versions"&gt;Python&lt;/a&gt; offers an upgrade every year. Using &lt;a href="https://en.wikipedia.org/wiki/Node.js#Releases"&gt;Node.js&lt;/a&gt; means you face a new version every six months!&lt;/p&gt;

&lt;p&gt;What I've experienced is that if I take a break from one of my projects and come back to it a year later, I'm faced with numerous hours of just upgrading the environment to get the darn thing to work!&lt;/p&gt;

&lt;p&gt;While keeping up with the latest security patches is important, I don't always need the latest and greatest features in the language. Sometimes I just want it to work so I can move on with whatever features I'm trying to implement. With containers, you can put a project on hold for a little bit and when you return, all the dependencies will still be there and, unless you depend on an external service, things should just work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolated development environments
&lt;/h2&gt;

&lt;p&gt;Another issue I have run into is having multiple versions of the language installed on my system. Python 2? Python 3? Node 12? Node 14? Many projects use a wide spectrum of these tools. Nothing drives me crazier than when someone comes up with a new shiny tool and asks me to install it globally!&lt;/p&gt;

&lt;p&gt;People have solutions for multiple environments on a single system. Just to name a few: &lt;a href="https://docs.python.org/3/library/venv.html"&gt;virtual environments&lt;/a&gt; for Python; &lt;a href="https://github.com/nvm-sh/nvm"&gt;nvm&lt;/a&gt; and &lt;a href="https://volta.sh/"&gt;volta&lt;/a&gt; for Node.js.&lt;/p&gt;

&lt;p&gt;Using Docker means that you can create a container that is dedicated to the environment and language version of your choice. Each of your projects can have different versions of languages and none of them will conflict with your host machine!&lt;/p&gt;

&lt;h2&gt;
  
  
  Easily repeatable development environments
&lt;/h2&gt;

&lt;p&gt;If you're working with multiple folks or just working with more than one computer, then you may appreciate that containers make sharing easy. All the instructions for building your container are checked into your source code repository.&lt;/p&gt;

&lt;p&gt;If a teammate or friend wants to collaborate with you on the project, just point them to the source code and they can have an exact replica of your container within minutes. No more fussing around with Windows vs Mac vs Linux desktops. They all can participate.&lt;/p&gt;

&lt;p&gt;Another scenario could be that you'd like to share your project in a demonstrable stage, but not share the source code. You can hand over the built container image and your friend can run an instance locally to check out your project. While this does not secure your source code, it does make sharing your project more palatable to folks who maybe are not developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easier Continuous Integration
&lt;/h2&gt;

&lt;p&gt;Since all the instructions on how to build your project are in the source code, then building the container image is very easy. You can build it on your personal computer. Or you can leverage one of the many cloud-based Continuous Integration (CI) offerings and have them build your container anytime you'd like. This is most helpful when working on a team,&lt;br&gt;
but it's important to point out that containers make this step much easier!&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud based hosting
&lt;/h2&gt;

&lt;p&gt;One of the biggest reasons to use containers these days is the plethora of online services that will host containers. The biggest drawback in the past was that even if you took the time and effort to make your project run in a container - you couldn't run it! At least you couldn't run it easily. &lt;a href="https://cloud.google.com/blog/products/containers-kubernetes/from-google-to-the-world-the-kubernetes-origin-story"&gt;Kubernetes&lt;/a&gt; fighting for developer mindshare with Mesos and Docker Swarm. Ultimately Kubernetes became very popular and now all major and many minor cloud providers offer some container hosting services.&lt;/p&gt;

&lt;p&gt;Based on your hosting needs, you can run a hobby project in the cloud for free (using various Free Tier offerings) or you can run up a robust and geographically redundant flotilla of containers for your production project. It's almost too overwhelming to choose from the options. In fact, Corey Quinn on the &lt;a href="https://www.lastweekinaws.com/"&gt;Last Week in AWS&lt;/a&gt; published a blog post on &lt;a href="https://www.lastweekinaws.com/blog/the-17-ways-to-run-containers-on-aws/"&gt;17 ways to run containers on AWS&lt;/a&gt; - and then wrote &lt;a href="https://www.lastweekinaws.com/blog/17-more-ways-to-run-containers-on-aws/"&gt;another blog post on 17 more ways&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless Ready
&lt;/h2&gt;

&lt;p&gt;Finally, using containers for your deployment allows you to fully embrace the Serverless movement. Allow me to explain the Serverless movement. In any commercial hosting relationship, you, as a project owner, delegate some responsibilities to your hosting provider. If you are renting physical rack space in a data center, then you allow them to provide a roof, power, and air conditioning. By using containers, you delegate a lot more to your provider. Essentially you are just renting CPU cycles from these container providers (although how you are billed varies from provider to provider).&lt;/p&gt;

&lt;p&gt;As a project owner, you are free of the responsibility of server management (mostly). If the load on your project goes up, then you just rent more CPU cycles. Conversely, if the load goes down, you rent fewer CPU cycles. This is the beauty of Serverless: the line of responsibility allows you to focus more on your project's delivered value and less on the boring tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;strong&gt;not&lt;/strong&gt; run Docker containers?
&lt;/h2&gt;

&lt;p&gt;Ok, so we've discussed the advantages of using containers. What about the downsides?&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Licensing
&lt;/h3&gt;

&lt;p&gt;According to the &lt;a href="https://www.docker.com/pricing/"&gt;Docker Pricing page&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Docker Desktop can be used for free as part of a Docker Personal subscription for: small companies (fewer than 250 employees AND less than $10 million in annual revenue), personal use, education, and non-commercial open source projects.&lt;/p&gt;

&lt;p&gt;Docker Desktop requires a per user paid Pro, Team or Business subscription for professional use in larger companies with subscriptions available for as little as $5 per month.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a nominal cost for most developers, but something worth considering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Desktop overhead
&lt;/h3&gt;

&lt;p&gt;When running Docker in the background, your computer will use more energy. I have found from personal experience that most modern computers handle the load easily.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only runs Linux
&lt;/h3&gt;

&lt;p&gt;Your Docker Container can only run Linux. That's because Docker takes advantage of &lt;a href="https://github.com/opencontainers/runc"&gt;runC&lt;/a&gt; which uses the Linux kernel. You can't run Windows &lt;strong&gt;inside&lt;/strong&gt; of Docker. But of course you can run Docker on a Windows host machine. If you are running a .NET project, you won't be able to use Docker. On the other hand, if you're running .NET Core then you're in luck!&lt;/p&gt;

&lt;p&gt;While you may think only using Linux would be limitation, but there are &lt;a href="https://en.wikipedia.org/wiki/Linux_distribution"&gt;literally hundreds of Linux distributions&lt;/a&gt; from which to choose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complex workflows
&lt;/h3&gt;

&lt;p&gt;Most developers are taught to develop locally on their machines. While this may be slowly shifting to online development, there is a lot of existing momentum. Exacerbating the problem are the myriad of options that Docker exposes to use the tool. It's extremely flexible and thus is quite complex to set up properly. Facing the task of setting up Docker from scratch is quite intimidating. It could cause some developers to have a bad experience and thus resist leveraging a fantastic tool.&lt;/p&gt;

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

&lt;p&gt;In the next posts, I would like to propose a flexible but opinionated developer workflow for the Python and Node.js environments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/edgarroman/opinionated-docker-development-workflow-for-nodejs-projects-part-1-26c0"&gt;Opinionated Docker development workflow for Node.js projects&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cover Photo by &lt;a href="https://unsplash.com/@ventiviews?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Venti Views&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/containers?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>node</category>
      <category>python</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
