<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Kevin Tran on Medium]]></title>
        <description><![CDATA[Stories by Kevin Tran on Medium]]></description>
        <link>https://medium.com/@tranduykhoa?source=rss-2b71aeaa58b4------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*fpXhIK9RJYPSeIivJRXVOA.png</url>
            <title>Stories by Kevin Tran on Medium</title>
            <link>https://medium.com/@tranduykhoa?source=rss-2b71aeaa58b4------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 18:56:54 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@tranduykhoa/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Tracking Email Activity from AWS Simple Email Service (SES)]]></title>
            <link>https://medium.com/@tranduykhoa/tracking-email-activity-from-aws-simple-email-service-ses-4ebdc8a9c04?source=rss-2b71aeaa58b4------2</link>
            <guid isPermaLink="false">https://medium.com/p/4ebdc8a9c04</guid>
            <category><![CDATA[nodemailer]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[email]]></category>
            <category><![CDATA[sns]]></category>
            <category><![CDATA[aws-simple-email-service]]></category>
            <dc:creator><![CDATA[Kevin Tran]]></dc:creator>
            <pubDate>Fri, 28 Jul 2023 20:16:48 GMT</pubDate>
            <atom:updated>2023-08-02T00:55:24.280Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kYNtAUp_vqpDTmqp" /><figcaption>Photo by <a href="https://unsplash.com/@maria_shalabaieva?utm_source=medium&amp;utm_medium=referral">Mariia Shalabaieva</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p>The email sending function is a common feature found in almost every website. For developers, <a href="https://docs.aws.amazon.com/ses/index.html">AWS SES (AWS Simple Email Service)</a> is a popular choice, particularly when the website is hosted on the AWS cloud. Although AWS SES doesn’t directly support email activity tracking, it does offer the possibility to create custom tracking features by integrating with other AWS services. In this post, I will explain how to build an application that tracks email sending activities using SNS HTTPs endpoint.</p><h3>SES Setup</h3><h3>Configure AWS SES</h3><p>Implementing email communication with AWS SES is a straightforward task, thanks to the support of various programming languages provided by <a href="https://aws.amazon.com/developer/tools/">AWS developer tools</a>. To send an email using SES, we can directly call the AWS SDK. Here’s an example using NodeJS:</p><pre>const client = new SESClient(config);<br>AWS.config.loadFromPath(&#39;./credentials.json&#39;); // for testing purpose<br>const command = new SendEmailCommand({<br>    Destination: {<br>      ToAddresses: [&#39;testemail@gmail.com&#39;],<br>    },<br>    Message: {<br>      Subject: { Data: &#39;Test Subject&#39; },<br>      Body: {<br>        Text: {<br>          Charset: &#39;UTF-8&#39;,<br>          Data: &#39;This is a test email&#39;,<br>        }<br>      }<br>    },<br>    Source: &#39;noreply@mydomain.com&#39;,<br>  });<br>  <br>  const response = await client.send(command);</pre><p>For production ready, I am using the <a href="https://nodemailer.com/">NodeMailer package</a> instead, which provides a slightly better interface:</p><pre>const ses = new aws.SES({});<br>const message = {<br>    from: &#39;noreply@mydomain.com&#39;,<br>    to: &#39;testemail@gmail.com&#39;,<br>    subject: &#39;Test Subject&#39;,<br>    text: &#39;body in text&#39;,<br>    html: &#39;&lt;p&gt;HTML content&lt;/p&gt;&#39;,<br>  };<br>  const transport = nodemailer.createTransport({<br>    SES: {ses, aws},<br>  });<br>  await transport.sendMail(data);</pre><p>Rather than directly calling the SES Client, NodeMailer redirects the API call to SES through the Transport object, making it easy to switch to different transportation options. This flexibility is particularly helpful for developers when running the code in development and test environments. We’ll explore this further in the <strong>Testing</strong> section later on.</p><h3>SES Dashboard</h3><p>In the code example, the app only needs to pass the necessary data, such as fromEmail, toEmail, subject, body (and attachments), to the function. AWS SES will then handle the entire process of sending out the email.</p><p>A pressing question arises: how can we determine whether the email has been successfully delivered to the user’s inbox or rejected by the user’s email server?</p><p>The <strong>SES Dashboard</strong> provides valuable insights into account usage, such as the total number of sent emails, historical sending data, rejected rate, bouncing rate, and complaint rate. However, these metrics do not provide specific details about each individual email activity. A complete email system requires this information to troubleshoot any potential bounces or rejections caused by external factors. Thankfully, the SES Notifications feature, integrated with AWS Simple Notification Service and AWS Simple Queue Service, helps bridge this gap and provides the necessary information for a complete email system.</p><h3>Complete email system</h3><p>Below is a draft design that outlines the components of an email system.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YUGimOR88MTKZtwC.png" /></figure><p>In our context, after observing how SES integration is set up in various applications, we have separated the Email sending system as a standalone application. This approach allows other applications to avoid managing their individual AWS configurations for email communication. Instead, they can simply make an API call to the email system, which will handle the entire lifecycle of the emails.</p><p>Here are some more details about each component:</p><ul><li><strong>API Gateway</strong>: allows client applications, SNS Subscriber (HTTP/HTTPS) and dashboard interacts with the Email System.</li><li><strong>Email Sending API</strong>: provides API to trigger email, it handles the Emails Service that responses for communicate with the AWS SES, and the Recipients Service that creates/updates the recipients’s profiles.</li><li><strong>Activity API</strong>: provides public endpoints for SNS subscriber to push the notification about the email’s activities. The notification will be proceed later by a background job processor to the domain data.</li><li><strong>Dashboard API</strong>: provides APIs for the Frontend Dashboard page to connect and render the email activities data.</li></ul><h3>AWS SES notification config</h3><p>In each SES Domain Identity, there is a <strong>Notifications</strong> config, where it allows to setup the SNS topic for each feedback type. Here is an example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*U95eax79uI9BqpE_.png" /></figure><p>Before setting up the SES notification, it’s essential to create the required SNS Topics. In the example provided, separate topics are used for each notification type (bounce, complaint, and delivery), but a single topic can suffice for simpler use cases.</p><p>The subsequent step involves defining the subscription for the SNS Topic, for which we’ll be using the HTTPs (or HTTP) protocol in this blog post. AWS SNS supports various other subscription types, including SQS (event queue), email, and SMS subscriptions.</p><p>To create the HTTPs subscription, select the subscription type and provide the endpoint URL. Please note that the endpoint URL should be publicly accessible on the internet, as private IP addresses won’t work.</p><p>In the example screenshot, the website supports creating an email hook, wherein each URL is assigned a unique identifier to distinguish email notifications from different identities. However, for simpler use cases, a general URL such as https://mydomain.local/ses_notifications will suffice.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/866/0*_8AwngyHKdaYE14z.png" /></figure><p>After creating the subscription, the SNS will make a request call with <strong>subscription_confirm</strong> message to the subscription URL. The application needs to store the request payload, which contains the Subscribe URL and the Subscription Token to <a href="https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-subscription-confirmation-json">confirm the subscription</a>.</p><p>To confirm the subscription, we can open the SubscribeURL on the browser or implement an automated logic that calls the <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/sns/command/ConfirmSubscriptionCommand/">ConfirmSubscriptionCommand</a> as follow:</p><pre>const input = { <br>    TopicArn: &quot;STRING_VALUE&quot;,<br>    Token: &quot;STRING_VALUE&quot;,<br>    AuthenticateOnUnsubscribe: &quot;STRING_VALUE&quot;,<br>  };<br>const command = new ConfirmSubscriptionCommand(input);<br>const response = await client.send(command);</pre><p>To avoid the message be formatted, SNS subscription allows to send the raw message instead of the formatted message. However, there could be some issue when reading the request body with the raw payload if the option <a href="https://docs.aws.amazon.com/sns/latest/dg/sns-large-payload-raw-message-delivery.html">“Enable raw message delivery”</a> was enabled. For example, if the project uses ExpressJS (NodeJS), this <a href="https://www.npmjs.com/package/raw-body">raw-body</a> package can be used to parse the request body, the default access to request.body doesn’t return any data.</p><p>Once the subscription is confirmed, whenever an email is sent using this SES domain identity, an <a href="https://docs.aws.amazon.com/ses/latest/dg/notification-examples.html">HTTP request</a> is triggered from SNS to the preconfigured endpoint. The email system captures the request payload and processes it to determine whether the email is delivered successfully.</p><p>The request payload may look like this:</p><pre>{<br>   &quot;notificationType&quot;: &quot;Complaint&quot;,<br>   &quot;complaint&quot;: {<br>      &quot;complainedRecipients&quot;: [<br>         {<br>            &quot;emailAddress&quot;: &quot;richard@example.com&quot;<br>         }<br>      ],<br>      &quot;complaintFeedbackType&quot;: &quot;abuse&quot;,<br>      &quot;arrivalDate&quot;: &quot;2016-01-27T14:59:38.237Z&quot;,<br>      &quot;timestamp&quot;: &quot;2016-01-27T14:59:38.237Z&quot;,<br>      &quot;feedbackId&quot;: &quot;000001378603177f-18c07c78-fa81-4a58-9dd1-fedc3cb8f49a-000000&quot;<br>   },<br>   &quot;mail&quot;: { // Content is trimmed }<br>}</pre><p>In this example, the notificationType is “Complaint” which indicates that the email reached the recipient’s mailbox but was marked as spam.</p><h3>Core components’ implementation</h3><p>I hope that I have covered the main concepts and functionalities of the Email System which integrates with AWS SES. In this section, we will explore the implementation of the core components in this email system.</p><h3>Email Sending Controller</h3><p>The controller exposes an API that allows other applications to make requests to send out emails. gRPC is used as a default choice for microservice protocol, the gRPC proto file looks like this:</p><pre>message TriggerEmailAttribute {<br>  string fieldName = 1;<br>  string value = 2;<br>}<br>message EmailAttachment {<br>  string name = 1;<br>  string remoteUrl = 2;<br>  optional string cid = 3;<br>}<br>message TriggerEmailRequest {<br>  string subject = 1;<br>  repeated string toEmails = 2;<br>  string mailBody = 3;<br>  repeated EmailAttachment attachments = 4;<br>  repeated TriggerEmailAttribute attributes = 5;<br>}<br>service EmailsServiceController {<br>  rpc triggerEmail(TriggerEmailRequest) returns (TriggerEmailResponse) {};<br>}</pre><p>The triggerEmail call receives an input of type TriggerEmailRequest and returns a response with type TriggerEmailResponse. The TriggerEmailRequest definition includes 5 fields used to make the call to the AWS SES API. In the implementation, the controller interacts with two services: the EmailsService, responsible for handling the email submission to AWS SES, and the RecipientsService, which manages the creation of recipients based on the provided information. The notifications received from the SNS Subscription are later mapped to these recipients.</p><p>By providing the toEmails parameter in the request object, the service allows for specifying multiple email addresses to receive the same email in a single request.</p><h3>Notification endpoints management</h3><p>As the Email sending service is used by multiple applications, each application has a distinct notification endpoint for AWS SNS to publish the email notifications.</p><p>To demonstrate how to support separate endpoints for each application, the entity relationship diagram is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/354/0*zKmV6tJKXN-pUams.png" /></figure><p>When an application makes an API call to the Email Sending Service, it includes the token in the meta fields to authorize the request, enabling the service to determine the email activity based on the hook id. Subsequently, when SNS subscription publishes the notification request to a hook, the system knows how to create the corresponding activity on the application’s usage.</p><h3>Background Job Processor</h3><p>Background jobs are crucial in modern software development for two main reasons. First, they handle long tasks without slowing down the main application, so users don’t have to wait. Second, they automatically try again if something goes wrong, minimize number of tasks gets lost or left undone.</p><p>The flow for the email sending and the hook notification processing looks like below:</p><ul><li>Send Email flow</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/537/0*vS9PPe9LasdAL_RG.png" /></figure><ul><li>Process notification flow</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/552/0*QsNm1uCKx41HmBMQ.png" /></figure><p>Let’s clarify the difference between another task scheduler system, where the processor acts as the master node distributing tasks to worker nodes, and the current case. Here, the processor is a consumer class with methods that process jobs in the queue or listen to events on the queue. For more information on tasks queue documentation, you can refer to the <a href="https://github.com/OptimalBits/bull">bull documentation</a>.</p><p>Data extraction from the notification can be done in the background as real-time updates are not required for analytic data. Parsing all the data from the payload may not be necessary, but we can store the notification for future data back-filling.</p><h3>Testing strategy</h3><p>Email testing can be expensive if actual emails need to be triggered. Fortunately, there are third-party services like <a href="https://mailcatcher.me/">MailCatcher</a> or <a href="https://mailtrap.io/">MailTrap</a> that provide email sandbox solutions, rendering sent emails in a web interface. This eliminates the need to trigger actual emails to test purposes.</p><p>While third-party email sandbox services are often sufficient, I opted for a slightly improved solution with <a href="https://github.com/maildev/maildev">MailDev</a>. Setting up MailDev for this project was straightforward, and I’ll document the configuration process here.</p><p>The maildev can be run in Docker, here is the Docker Composer service definition:</p><pre>maildev:<br>    image: maildev/maildev<br>    expose:<br>      - &quot;1025&quot;<br>    ports:<br>      - &quot;51080:1080&quot;</pre><p>Nothing is fancy here. The maildev service exposes the port 1025 for the Email App to connect, and allows the traffic to port <strong>1080</strong> (<strong>51080</strong> from host) to access the dashboard.</p><p>Since we use <strong>NodeMailer</strong> package, it creates a <strong>Transport</strong> object to connect to AWS SES API. To make the app send emails to <strong>MailDev</strong> in Dev environment, we need to create another transport object and hook it during the initialization.</p><p>The CreateTransportService takes in a driver parameter, which indicates the type of transport, the code looks like below:</p><pre>enum MailDriver {<br>  ses,<br>  maildev,<br>}<br><br>export class DummyTransportation extends StreamTransport {}<br>export class CreateTransportService {<br>  constructor(private readonly options: TransportServiceOptions) {}<br>  create() {<br>    switch (MailDriver[this.options.driver]) {<br>      case MailDriver.ses: {<br>        const ses = new aws.SES(this.options.sesConfig);<br>        return createTransport({<br>          SES: { ses: ses, aws: aws },<br>        }).transporter;<br>      }<br>      case MailDriver.maildev: {<br>        return createTransport({<br>          ...this.options.mailDev,<br>        }).transporter;<br>      }<br>      default: {<br>        return new DummyTransportation({<br>          streamTransport: true,<br>        });<br>      }<br>    }<br>  }<br>}</pre><p>The mailDev options object is simple with port and host, which can be passed into the app through a configuration file:</p><pre>maildev:<br>    port: 1025<br>    host: maildev</pre><p>To speed up the test running time, we can defined the DummyTransportation to create mock Transportation when running test.</p><h3>Capacity calculation</h3><p>Understanding the traffic and data is important to determine the actual value of the platform we have developed. The application primarily focuses on handling email sending requests from various applications within the organization. While it also offers a dashboard to monitor email activities, its main functionality revolves around efficiently managing the email communication.</p><p>To demonstrate, I will give an example how to calculate the capacity of the email system.</p><ul><li>Sending rate: assume the app sends out 1 email per second, or 86k emails per day, 2.6M per month.</li><li>For each email, the system stores the activities data and the recipients data. The recipient data could be like this:</li></ul><pre>// TypeORM syntax   <br>@PrimaryGeneratedColumn(&#39;increment&#39;)   <br>readonly id: number;   <br><br>@Column({ unique: true })   <br>email: string;   <br><br>@Column(&#39;json&#39;)   <br>attributes: RecipientAttribute[];   <br>readonly createdAt: Date;   <br><br>// other columns</pre><ul><li>Each row recipient takes ~20bytes, we can assume if there are 10M of users in all managed applications, the total size of the recipients table is around <strong>200MB</strong>, which is very affordable.</li><li>In the another side, the activities table could be a little bigger as the data is keep increasing. Let’s assume each activity takes around 30bytes, the size of the activities table would be:</li><li>in a month: 2.6M * 30bytes = 78MB</li><li>in a year: 1GB</li><li>in 3 year: 3GB</li></ul><p>While storage is not a concern, the sequential scanning of the activities table may result in slow performance. On a 1GB/sec (SSD disk) speed, generating complex dashboard data queries could take up to 3 seconds without optimization. Fortunately, the use of database indexes can greatly improve query times.</p><p>Overall, the application remains well-suited for the next three years, considering its current sending rate and user base.</p><h3>Conclusion</h3><p>In conclusion, I hope you found this blog post insightful and informative. We’ve discussed some of the key aspects of the centralized Email service, though it’s important to acknowledge that there are many other considerations, such as integrating it with different app clients, data tracking, and storage usage, among others. As someone who has been involved in this project from its inception, I feel fortunate to have had the opportunity to design the application, define its features, and develop a comprehensive plan. Implementing a robust and efficient Email system can greatly enhance communication and efficiency. However, it’s important to acknowledge the challenges that arise with the complexity of email configuration.</p><p>I’m thrilled to share this approach with you, and I hope it inspires you to enhance your organization’s platform. Thank you for taking the time to read this!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4ebdc8a9c04" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Setup local domain with self-signed SSL Certificate]]></title>
            <link>https://medium.com/@tranduykhoa/setup-local-domain-with-self-signed-ssl-certificate-e126d65b7107?source=rss-2b71aeaa58b4------2</link>
            <guid isPermaLink="false">https://medium.com/p/e126d65b7107</guid>
            <category><![CDATA[ssl-certificate]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Kevin Tran]]></dc:creator>
            <pubDate>Wed, 28 Jun 2023 20:05:43 GMT</pubDate>
            <atom:updated>2023-06-28T20:08:56.968Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wm2LZItVL67Pj-d2" /><figcaption>Photo by <a href="https://unsplash.com/de/@introspectivedsgn?utm_source=medium&amp;utm_medium=referral">Erik Mclean</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Objective</h3><p>The local domain are helpful to setup the connection between the Frontend and Backend on the development machine. It is also a common solution when the actual address and port is difficult to remember. In this post, I will explain how to set up the local domain and config the SSL certificate on the MacOS laptop.</p><h3>Software required</h3><p>In order to config the local domain name and issue the SSL cert, we’ll need these tools installed:</p><ul><li>Openssl</li><li>MacOS Keychains app</li><li>Nginx: for configuring the proxy</li><li>Admin account</li><li>vi: to edit the host and create the configuration files</li></ul><p>The nginx and vi are just my preference, you can use VSCode, emacs for editor and Apache, HA proxy for the proxy.</p><h3>Create the local domain</h3><p>Setup the domain is the most simple task, you can open the hosts file with the admin privilege.</p><pre>sudo vi /etc/hosts</pre><p>Add a line for the new domain and map it to <strong>127.0.0.1</strong></p><pre>127.0.0.1 mydomain.local</pre><p>If you run the server inside a virtual network, for instance by running the proxy in the Virtual box or Docker, then use the IP of the virtual network interface instead.</p><p>After that, you may flush the DNS cache by using this command:</p><pre>sudo dscacheutil -flushcache</pre><h3>Create the Root Certificate Authority (Root CA)</h3><p>The SSL in the normal website are issued by a trusted Certificate Authority. However, for the local development, we don’t need to purchase a SSL (and we don’t own the domain), hence we have to setup the Root CA as well. During the handshake step, the browser checks whether the cert is valid by asking the Root CA about the cert, and if the Root CA doesn’t confirm with the browser, the browser marks the site as unsafe.</p><p>To generate the Root CA, we can use the <strong>openssl</strong> tool. The steps are:</p><ol><li>Generate the root CA key</li><li>Generate the root CA cert</li></ol><p>Personally, I suggest to create a directory to store all the files that are created during this process together, I usually create one in /tmp/ssl for this purpose. All the files we create after this are in the same directory.</p><p>To start with, we have to create the RootCA configuration file.</p><pre># fileName: config.conf<br>[req]<br>prompt = no<br>distinguished_name = req_distinguished_name<br>req_extensions = v3_req</pre><pre>[req_distinguished_name]<br>C = CA<br>ST = Kitchener<br>L = ON<br>OU = Org<br>CN = mydomain.local<br>emailAddress = dev@mydomain.local</pre><pre>[v3_req]<br>basicConstraints = CA:FALSE<br>keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment<br>subjectAltName = @alt_names</pre><pre>[alt_names]<br>DNS.1 = mydomain.local<br>DNS.2 = api.mydomain.local<br>DNS.2 = <a href="http://www.mydomain.local/">www.mydomain.local</a></pre><p>Please modify the content of the file to match with your information and the local domain. The file references and examples can be found from <a href="https://www.ibm.com/docs/en/hpvs/1.2.x?topic=reference-openssl-configuration-examples">IBM OpenSSL configuraton</a> and <a href="https://www.openssl.org/docs/manmaster/man5/config.html">OpenSSL document</a></p><p>After creating these 2 config files, we can generate the rootCA key and cert with these 2 commands</p><pre>openssl genrsa -out rootCA.key 2048<br>openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem -config config.conf</pre><p>The values of each parameters can be altered by different values, such as the number of days can be change to 365, or the algorithm can be set to <strong>sha512</strong>. For the details of each parameters, checkout the OpenSSL manual.</p><h3>Issue the Self-signed SSL cert</h3><p>Create the ext file in the same directory with the content like below</p><pre># filename: mydomain.local.ext</pre><pre>authoritykeyIdentifier = keyid,issuer<br>basicConstraints = CA:FALSE<br>keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment<br>subjectAltName = @alt_names</pre><pre>[alt_names]<br>DNS.1 = mydomain.local<br>DNS.2 = api.mydomain.local<br>DNS.2 = <a href="http://www.mydomain.local/">www.mydomain.local</a></pre><p>After this, we can generate the cert key file and the cert signing request file</p><pre>openssl genrsa -out mydomain.local.key 2048<br>openssl req -new -key mydomain.local.key -out mydomain.local.csr -config config.conf</pre><p>The last step is to issue the cert</p><pre>openssl x509 \<br>        -in mydomain.local.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial \<br>        -out mydomain.local.crt -days 1024 -sha256 -extfile mydomain.local.ext</pre><p>After that, you should have the the cert (and other files) in the temporary directory. We don’t need the config file as well as the ext file. A common practice I usually take is to move these files to a more permanent place, such as /etc/ssl, so the files aren’t gone after reboot. To import the cert to the <strong>Apple Keychains</strong>, we can either use the Keychains UI or by running this command</p><pre>sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /etc/ssl/rootCA.pem</pre><p>Noted that we don’t have to import the cert for the local domain name.</p><h3>Config the proxy</h3><p>We can configure the SSL cert for the website from the proxy level instead of the app server. However, if you prefer to configure the cert from the app, please skip this section.</p><p>I prefer to use Nginx for the proxy because it is easy to setup and the document is excellent.</p><p>Here is how the Nginx config look like</p><pre>#filename: /opt/homebrew/etc/nginx/servers/mydomain.local.conf<br>upstream frontend {<br>   server localhost:3000;<br>   keepalive 100;<br>}</pre><pre>upstream backend {<br>   server localhost:8888;<br>   keepalive 100;<br>}</pre><pre>server {<br>  listen 443 ssl;<br>  server_name mydomain.local <a href="http://www.mydomain.local;/">www.mydomain.local;</a></pre><pre>  ssl_certificate     /etc/ssl/mydomain.local.crt;<br>  ssl_certificate_key /etc/ssl/mydomain.local.key;</pre><pre>  # locations config</pre><pre>  location / {<br>    proxy_http_version 1.1;<br>    proxy_pass <a href="http://frontend/;">http://frontend/;</a><br>  }</pre><pre>  # allows large request body for upload location</pre><pre>  location ~ /uploads/(file|image) {<br>    proxy_http_version 1.1;<br>    client_max_body_size 50M;<br>    proxy_pass <a href="http://frontend/;">http://frontend/;</a><br>  }<br>}</pre><pre>server {<br>  listen 443 ssl;<br>  server_name api.mydomain.local</pre><pre>  ssl_certificate     /etc/ssl/mydomain.local.crt;<br>  ssl_certificate_key /etc/ssl/mydomain.local.key;</pre><pre>  # location config<br>  # the backend use Apache Web server, the headers are required</pre><pre>  location / {    proxy_set_header Host $host;<br>    proxy_set_header X-Real-IP $remote_addr;<br>    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br>    proxy_set_header X-Forwarded-Proto $scheme;<br>    proxy_set_header Access-Control-Allow-Origin *;<br>    proxy_http_version 1.1;<br>    proxy_pass <a href="http://backend/;">http://backend/;</a><br>  }<br>}</pre><p>With Nginx, we can setup complicated routing rule to for the website traffic and configure the SSL without hassle.</p><p><strong>A pro tips</strong>: If nginx is installed through <strong>brew</strong>, we can use the <strong>brew services</strong> to make it starting automatically when the laptop is boosted (<strong>brew services start nginx</strong>).</p><h3>Recap</h3><p>This short post is a summary for the steps to setup the local domain on the local machine. I hope you find it useful for your works. And if you have set up the same stuffs in the Linux machine, please share the steps with me.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e126d65b7107" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[It’s okay to miss things]]></title>
            <link>https://medium.com/@tranduykhoa/its-okay-to-miss-things-a4b4c5454683?source=rss-2b71aeaa58b4------2</link>
            <guid isPermaLink="false">https://medium.com/p/a4b4c5454683</guid>
            <category><![CDATA[communication]]></category>
            <category><![CDATA[to-do-list]]></category>
            <category><![CDATA[productivity]]></category>
            <dc:creator><![CDATA[Kevin Tran]]></dc:creator>
            <pubDate>Sun, 13 May 2018 04:50:18 GMT</pubDate>
            <atom:updated>2018-05-13T05:13:35.128Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CBq29fQpuDrrZHEt7ejXVg.jpeg" /></figure><p>The internet technology changes the communication completely. It takes milliseconds to deliver your message to someone, the receiver can see your message in less that a second. We receive a hundred (plus) of notifications everyday from different communication apps, news, social network feeds. To read and answer them (and reply) isn’t an easy job. This is my idea to survive in the notification flood.</p><p>9.30 AM, I arrive to the office. I go to my desk and turn on my laptop, checking Slack, Github Pull Requests and notifications, checking emails, writing some updates on Slack (just like post a FB status everyday).</p><p>If nothing’s special, I start a day with my daily todo list, prioritize important things to be done. It take a bit of time but the returned value is significant. If you are doubt about it, read the book <a href="https://www.briantracy.com/catalog/eat-that-frog-3rd-edition">Eat that frog</a>.</p><p>Then I review my colleague Pull Requests. It usually takes a bit longer than 30 mins.</p><p>I take a break and start working on my tasks.</p><blockquote><em>PING!</em></blockquote><p>A beautiful TCP package is delivered to my machine. With a natural reaction, I acknowledge by saying Hi/Morning — means “Yes I received the package”. I don’t want the man-to-man connection takes too long to response, it makes people feel <strong>my</strong> communication system is unreliable.</p><p>The handshake protocol is successfully setup, the information exchanging process start happening in a TCP socket.</p><p>Some more TCP packages are broadcast in different channels.</p><p>Thanks for modern web browsers to show a little banner on my top right screen, I get notified when someone is talking somewhere. Just simply clicking on the banner, I can follow them to the place that conversation is happening, which saves me time to figure out.</p><p>I also use smart phone, it’s even more helpful. All the notifications are there, I can scan through each notification and knowing where are they from. Joining each conversation, making sure I don’t miss anything, provide useful information to my colleagues can’t be easier.</p><p>What is the time for me to focus on my own stuffs? Seem like not much.</p><p>Different with the TCP protocol, which is indefinitely waiting until the reply message arrive. A retry mechanism could be triggered if there is no response after a certain time (how long is depended on the patience configuration), another TCP package could arrived using the same system, sometimes can be in different systems, or back to the old slow Dial-up connection. And sometimes it can switch to UDP protocol.</p><p>I used a bit of metaphor in talking of TCP/UDP, I hope you don’t feel annoyed.</p><blockquote><em>Having Slack open all day is truly death by a thousand paper cuts for your productivity. </em>– <a href="https://twitter.com/iamdevloper/status/977139158731972608">I am Devloper</a></blockquote><p>That hurts.</p><p>I don’t hate distraction, another way to say is I don’t hate people annoying me when I am working. I hate myself when I can’t finish my works as promised. I have to kill distraction to boost up my productivity.</p><p>I start re-reading the book “<a href="https://www.amazon.com/Productive-Programmer-Theory-Practice-OReilly/dp/0596519788">The Productive programmer</a>” of Neal Ford. A good book for every developer who wants to improve productivity. It’s important to read many books as we can, and not necessary to remember everything from the book. When we face a problem, we know there is a book about it, we can always go back to the book. Now I am taught this lesson well.</p><p>Personally, I prefer the concept of the book rather than the specific technique and example. I take those concepts from the book and apply them to my current working flow.</p><p>One of the techniques mentioned in the book is the “Quiet time”. It’s the time span that we can focus on our works.</p><p>For my current job, I can find 2 quiet time spans from 11AM — 12PM (1 hour), and 1.30PM — 3PM (1.5 hour) and sometimes 4.30PM — 6PM (1.5 hours). Between these time spans, I can do different works that are less affects to the key metrics.</p><p>The technique works best when people can maximize their performance during the short period. I can drink a soft-drink or coffee to maximize my focus. Based on some researches, after drinking coffee, we may feel a bit sleepy during first 30 mins, which exactly the time needed to get into <a href="https://en.wikipedia.org/wiki/Flow_(psychology)">a flow</a></p><p>3 questions I have to answers before start my quiet time</p><ol><li>What will be done?</li><li>How important it is?</li><li>Is it affected to the key metrics?</li></ol><p>What can I get done in an hour and a half is a good question. Human attention doesn’t last long. If I can’t find out what I can get done during a quiet time, I am better to not enter the quiet time, but go back and breakdown the task, clarify the requirement and the scope again. It’s better to figure out them before working on the story.</p><p>The second question is how important my work is. Sometimes I can spend the whole day to fix a CSS. This is important to me, but not for the team. However, it doesn’t apply for all times. For an example, a CSS is written by you, and it doesn’t written well, and your colleagues start copying this CSS to many places. Now it’s bad, it start bothering you whole day and night. If I were in this situation (yes, I am), I wish I could write it properly from the beginning.</p><p>Don’t misunderstand the importance of the task with the value of the task to the key metrics. Writting document is valueable, but it’s hard to measure in term of key metrics. The key metrics are designed to show the quality of the product, that can be understood by anyone include people without technical background.</p><p>In an Agile company, burn down rate, velocity, rejection rate, … are the important metrics. Some people suggest to use customer satisfaction and revenue as the development team metrics as well. The customer satisfaction is count by the error rate, the reliability of the system, the time it takes to release new feature… However, those metrics depends on vary costs and it’s more about the overall company effectiveness.</p><p>I don’t disagree with the idea of using those key metrics, and encourage each person thinking them before putting a story to icebox, bottom of backlog and start working on it. Yeah, we have 3 times to think about those business metrics.</p><p>And of course, by talking about produce-wide metrics, I don’t mean to sacrify the development metrics. It’s about the maintenance cost, testing cost, training cost…</p><blockquote><em>Any fool can write code that a computer can understand. Good programmers write code that humans can understand. — Martin Fowler, </em><a href="https://twitter.com/codewisdom/status/951529719937323010?lang=en"><em>CodeWisdom</em></a></blockquote><p>It tells us something. Software development is man to man work. The product of it brings values to both team and product. We may deliver a lot of feature with a ton of technical debts and neglect the maintainance cost.</p><p>Unless people tell us to neglect the cost of maintenance and we know it isn’t a lie, then we can go ahead to create as much of technical debt, hacky stuffs as we “have to”.</p><p>Most of the time customer doesn’t know what they want, we can’t use them to excuse for create a legacy system. Remember the story of <a href="https://en.wikipedia.org/wiki/The_Scorpion_and_the_Frog">scorpion and a frog</a>. Be aware of. Don’t neglect our team metrics, our values on building maintainable software. And <a href="https://www.amazon.com/Clean-Coder-Conduct-Professional-Programmers/dp/0137081073">learn to say no</a> when we have to.</p><p>To summarize, productivity is a result of technique, planning, and execution. Quiet time technique create quality time spans to produce the most important works in limited of time. Planning ahead helps us evaluate what is really important, it allows us to focus on an individual task at a time. The final step is execution, it’s the most important step to produce result. There is always enough time to raise the standard of code quality, not cutting the corner if we have discipline of following the first two steps to create the quiet time and planning, prioritize the works.</p><p>The opening argument isn’t the main focus of this post. There is nothing wrong with the internet, nothing is wrong with the communication system. If we know to take values from, it’ll help us improve our productivity. It’s more important to learn how to use those key metrics to measure our performances, and the distractions from the communication system is no longer a problem.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a4b4c5454683" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Get from a day]]></title>
            <link>https://medium.com/@tranduykhoa/get-from-a-day-6d45228fac5b?source=rss-2b71aeaa58b4------2</link>
            <guid isPermaLink="false">https://medium.com/p/6d45228fac5b</guid>
            <category><![CDATA[life]]></category>
            <category><![CDATA[self]]></category>
            <category><![CDATA[personal-development]]></category>
            <dc:creator><![CDATA[Kevin Tran]]></dc:creator>
            <pubDate>Sat, 08 Jul 2017 05:19:23 GMT</pubDate>
            <atom:updated>2017-07-13T05:01:14.635Z</atom:updated>
            <content:encoded><![CDATA[<p>I spend half of my life with code, and another half with coffee.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G4j-NUaw4x9dAwdVl066tA.jpeg" /></figure><p>The first part is more than true. I Code for work, code for fun, code for free, and code cause I don’t know what else to do.</p><p>Imagine how short a day is. For developer, it’s extra short when we have a bug.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/264/1*UMsr1Q9V3wFp9cZPMOTIVQ.png" /></figure><p>Some days we just want to get through. I meet some guys, who always count how many days til TGIF.</p><p>We may neither finish anything, nor learn anything in a bad day. These bad days won’t go away if we don’t know how to turn them off. They just come back again again everyday, until we switch the situation, or hope someone can help us turn if off.</p><p>I also have some bad days like this. But I think one day I live is one day I lost. And I know I can’t live too long (may be 5 of 5 years ahead), I want to enjoy every single day, learn something from it, even a bad day, instead of just give it away.</p><p>One way to treat your bad day is…</p><p>Reading the book “<a href="https://www.bookdepository.com/Dont-Sweat-the-Small-Stuff-Richard-Carlson/9780733800849?redirected=true&amp;utm_medium=Google&amp;utm_campaign=Base1&amp;utm_source=SG&amp;utm_content=Dont-Sweat-the-Small-Stuff&amp;selectCurrency=SGD&amp;w=AF7JAU9SS9252VA80CGNA73Y&amp;pdg=kwd-309174813119:cmp-711782081:adg-35553535805:crv-164049165286:pid-9780733800849&amp;gclid=CjwKEAjw4vzKBRCt9Zmg8f2blgESJADN5fDgc5o8tRmd5lz5KWno1t6qPjqbTDV7IaSsupgwdjggdhoCi2bw_wcB">Don’t sweat the small stuff</a>”. There are many good advices in this book, and I would to recommend you to check it out.</p><p>These techniques in the book are easy to use, but require us to practice again. Otherwise when we loose your motivation, or can’t control the emotion, we’ll forgot or ignore them, we only follow our emotion and end up worst.</p><p>Another thing that I learn is a bad day happens, I remind myself it’s opportunity for me to apply some techniques from the book, and evaluate myself if I get angry easily (and say to myself “Failed to apply. Not enough skill yet”). Be an audience of your life, not a actor/actress.</p><p>Last but not least. Be prepared.</p><p>My “adopted” father has a talk on preparation, included prepare for a good day. He said: Life is designed to not give us what we want, not give us what we need, but life is designed to give us what we deserved.</p><p>Here is his talk on this. Recommend you to watch, just cost you 5 mins</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F2w3klYeMik8%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D2w3klYeMik8&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F2w3klYeMik8%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/8081fd6082cbd57b354cf1f97193e57f/href">https://medium.com/media/8081fd6082cbd57b354cf1f97193e57f/href</a></iframe><p>That’s all I want to share today. Hope you enjoy it. I wish we all learn something from this post and from the next day.</p><p>P.S. The original link <a href="https://kevintran.ninja/posts/get-from-a-day-2017-07-05">to my post</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6d45228fac5b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Make it as a habit to challenge yourself.]]></title>
            <link>https://medium.com/@tranduykhoa/make-it-as-a-habit-to-challenge-yourself-6d3b23d3cab6?source=rss-2b71aeaa58b4------2</link>
            <guid isPermaLink="false">https://medium.com/p/6d3b23d3cab6</guid>
            <category><![CDATA[personal-development]]></category>
            <category><![CDATA[challenge]]></category>
            <dc:creator><![CDATA[Kevin Tran]]></dc:creator>
            <pubDate>Sun, 28 May 2017 05:21:25 GMT</pubDate>
            <atom:updated>2017-05-28T05:28:42.547Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RgBGTGU1sv-nuZ_9-qxGPg.jpeg" /></figure><p>Hi everyone, it’s such a long time (10 months omg) I haven’t written anything.</p><p>I built this blog myself on Rails, and host it in Digital Ocean. It was a cool project, I am both developer and user, I understand how each feature values to users.</p><p>However, from time to time, it becomes difficult to maintain the project. Many times I want to write a post, and I found some bugs. Then I spent my 2 hours to fix bugs instead of doing what I tend to do. It is demotivated me a lot whenever I want to write.</p><p>I decide to stop maintain my blog and rebuild it using Github page and focus on content more than code. I spent a lot of times to develop my blog. It was tough to make this decision.</p><p>Now I have a new blog using Github page, Jekyll plugin. There is no excuse not writing blog post because there is a bug. There’re still bugs, but the cost of maintenance the site is much less than.</p><p>I think writing content is more valuable than maintain it. It was hard to make this decision, I love the project, and I was not sure I enjoy writing content in markdown syntax. Also this time I haven’t know how to config the website in Jekyll. After moving, I know that I was too conservative. I should have more changes in life and make it as a habit. And that’s all I want to share in this post.</p><p>We want to change our life, fix our bad habit to live better. We want to get out of our comfort zone. We want to have something we don’t have. So, we must do something that we haven’t done before.</p><p>Knowing this lession is important. However, there is one more thing to know, we don’t like changing. We know the benefit of changing, we don’t change.</p><p>For me, I am afraid of changing.</p><p>I love my comfortzone. As a developer, I love my programming language I am using, I love the framework I am using. I love the testing framework I am using. Ah, Just kidding, It is not I love or dont’ love, I am confident with them, I know I can get thing done by these things. I rarely use different languages, different frameworks, cause I am sure it supports well for my job.</p><p>I am not good at swimming. I learn swimming because I want to swim with my girlfriend. Sometimes I want to swim, but go to swimming complex and swim with many professional swimmers is quite stressful.</p><p>I am good a badminton. I may win or lost, but I am pretty sure I play well, cause I play badminton for almost 5–6 years. If choosing between badminton and swimming, definitely I go with badminton, safe choice.</p><p>Another example, I always call Ice Latte when I go to the coffee. I tried many coffee shops just to figure out the best Ice latte After having many ice lattes, it becomes a safe choice for me whenever I go to coffee shop. Just want to see a new coffee shop’s ice latte is better.</p><p>The last example may not be relevant, just because I am having an ice latte now, so I put it in my post ^^. I use these examples to prove a point, if we’re confident about one thing, we prefer to go with it instead of other options. It isn’t bad, it just limit us to try new things, and it make a habit not to try.</p><p>It is so hard to try new thing, even though I have methods, I have motivation, I have some reasons. I did hard to found them, but I still don’t take action, because I don’t have the habit of doing new thing. I feel not enough to take action yet.</p><p>My solution for this is challenge myself more often. I read this blog <a href="https://themission.co/a-list-of-30-day-challenges-you-can-start-today-532c28bc2585">https://themission.co/a-list-of-30-day-challenges-you-can-start-today-532c28bc2585</a></p><p>At first, it sounds a bit weird. However, it’s exactly what I don’t have, that’s why I feel nervous to change, to challenge myself to do something I haven’t done before. I may not do anythings in this list, I am not able to do it for 30 days also, I just want to make it happened once</p><ul><li>Cycles 12kms nonstop</li><li>Write a non-tech blog</li><li>Talk to my ex-girlfriend</li><li>Give free advice to some strangers</li><li>Order something different with Ice Latte</li><li>Give a talk about something that isn’t related to my experience or knowledge</li></ul><p>And here is my list to do next</p><ul><li>Contribute to a Golang lib</li><li>Pushup 100 times nonstop</li><li>Writing a blog about Psychology or personal development</li><li>Go some places alone</li><li>More things to come</li></ul><p>The point here is: feel more naturally to challenge yourself, to build a habit to try new thing, even it’s small thing. Then there is more opportunity for you, you can do anything with full confident and belief to create a better life.</p><p>P.S. This is my non-tech blog post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6d3b23d3cab6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Docker-machine makes things harder in OSX because of file ownership]]></title>
            <link>https://medium.com/@tranduykhoa/docker-machine-makes-things-harder-in-osx-because-of-file-ownership-542c6c26c59e?source=rss-2b71aeaa58b4------2</link>
            <guid isPermaLink="false">https://medium.com/p/542c6c26c59e</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[containers]]></category>
            <dc:creator><![CDATA[Kevin Tran]]></dc:creator>
            <pubDate>Tue, 26 Apr 2016 02:49:40 GMT</pubDate>
            <atom:updated>2016-04-26T02:50:30.591Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Docker-machine makes things harder in OSX because of file ownership</strong></p><p>Starting working with docker waas really amazing. I pulled several images and ran it in my machine, 100% of which worked correctly. I found this the way to go for my career: stick to Docker. I used a quote from the Polymer community: there will be a “container” for it. Definitely true, almost all services I use to run my website are available in Docker hub or Github. I had no problem to use it, a bright future opens in front of me. Until…</p><p>I am using mac to develop software, and docker only supports native Linux kernel, which means I need to install Docker in a Virtual machine. Fortunately I don’t need to do it manually because of docker-machine, they allow me to use a tiny linux with built-in docker, also to map the `home` directory in my mac to /Users inside the machine, which is perfect.</p><p>At first, it took a while to understand how to use the command, the concept of image vs. container, how to use docker-compose to automatically run all the containers, link them together and get the website running; but this wasn’t the hardest part. I got frustrated of the volume part for a long time. In this post, I don’t try to explain the volume part in docker. I just want to share with you how I understand about it and my mistakes when using volume in an incorrect way.</p><p>This is how I run the rails container:</p><pre>docker run -d --rm -v `pwd`:/myapp -p3000:3000 -t my_web_image bundle exec rails s -b 0.0.0.0</pre><p>Yeah, it works. Of course this command doesn’t link with database container, just a stand alone one. Believe it or not, it took me a year to write that :cry:. This command uses -v flag to set the volume from the current directory to /myapp directory in the container, so that I don’t need to restart the container every time I make a change. Of course, in the Dockerfile, I need to run my webapp in /myapp directory, and of course, don’t use ADD or COPY.</p><p>That’s the basic volume concept in docker. I think after you see my command, you can write it yourself for many containers. I myself use this command to run many containers for a long tims, and I forgot about the mapping /Users stuff in my VM in order to make docker work.</p><p>Several weeks ago, I had to use the mysql image to run a new project. Turned out this one didn’t work with the command I was using for a…a decade. It just said the data wasn’t accessible. So I checked the permission in my mac. Maybe something’s wrong, so I gave the directory full permission 766, but still didn’t work. After many days of continuously checking many documents, including github issues, blogs, and docker hub, I found out this was the problem with the running user in the docker images.</p><p>After I understood the problem, I thought it’d be easy fix. I was so naive to try to change the file ownership in my mac and hoped :)). But you know what? In my macbook, this directory is owned by `kevin:kevin`, but in docker-machine, it’s own by `docker:staff`, which is different. Even when I tried to create a new user and group in my mac, then assigned the new user+group to this folder, it still kept the same `docker:staff` in the docker-machine. You may ask why I didn’t change the permission inside docker-machine instead, the problem would be every time you stop the machine, things are gone. So I’m smart enough to not try to do it. Actually I did try to add some config file for another docker images in my docker-machine before, and it was just gone after restart. I know it doesn’t work, and if it works, it isn’t the most efficient way to do.</p><p>I got trouble with it in a couple of weeks. I almost gave up with docker, and even said to myself that docker was only for linux users man. Can’t use Docker in OSX, or I need to install Ubuntu and use Vagrant with docker, which makes thing harder than it should be. May be it’s just a nice toy, but not usable.</p><p>When I almost gave up on docker, I found an unofficial docker for mysql for macosx. It just works perfectly, so I was very curious to understand how the creator fixed it. Then I spent a few hours to understand his dockerfile, and realized that maybe this is the only way to do, and no one mentioned it in the docker document yet, so I want to share it with you how he does it, and what we can do to overcome this situation. Don’t be like me last time haha.</p><p>Remind of the situation:</p><p>In my mac</p><p><strong>./project_x kevin kevin</strong></p><p>In docker</p><p><strong>./myapp docker(1000) staff(50)</strong></p><p>And in the mysql image, they use user mysql (id 999) to run.</p><p>So what he did in his custom image is:</p><pre>ENV BOOT2DOCKER_ID 1000 <br>   ENV BOOT2DOCKER_GID 50<br>   RUN useradd -r mysql -u ${BOOT2DOCKER_ID} &amp;&amp; \     usermod -G staff mysql  <br>   RUN groupmod -g $(($BOOT2DOCKER_GID + 10000)) $(getent group $BOOT2DOCKER_GID | cut -d: -f1) RUN groupmod -g ${BOOT2DOCKER_GID} staff</pre><p>What does it mean?</p><p>First, he created user mysql with id 1000, also added user to group staff, the same with the user id with the ownership in docker-machine. Also he added the user to group staff, which made the ownership in docker-machine and the docker-image consistent. Right now, the ownerships in both environment look like.</p><p>docker-machine</p><p><strong>/myapp docker(1000) staff(50)</strong></p><p>docker-image</p><p><strong>/myapp mysql(1000) staff(xxx)</strong></p><p>The user’s name does not matter, just the user id. The final thing we need to change is the group id, which is occurred by the last 2 commands. This guy tried to go extra step by finding the current group holding id 50, then changed this group first before assign id 50 for staff. I wonder why he didn’t do it for the user id as well :).</p><p>That’s it, I think you got the idea now. If you understand this code, you can do the same with other images, although it may not be easy. Also, since there is a different approach with ` — volumes-from`, you can also try to fix the issue inside the data volume. I can’t tell you it would work or not, so make sure your hands are dirty.</p><p>I hope my post will help you have a better look on docker, and give it a try for your current/next project. Keep calm and containerize it.</p><p><strong><em>The Dockerfile I use in my blog is </em></strong><a href="https://github.com/dgraziotin/osx-docker-mysql"><strong><em>https://github.com/dgraziotin/osx-docker-mysql</em></strong></a><strong><em>.</em></strong></p><p><strong><em>UPDATE: Docker has release the beta version Mac. Hopefully it can help to eliminate the problem I have. Check out this post for more information</em></strong></p><p><a href="https://medium.com/@nzoschke/docker-for-mac-beta-review-b91692289eb5#.pyn86ic7x"><strong><em>https://medium.com/@nzoschke/docker-for-mac-beta-review-b91692289eb5#.pyn86ic7x</em></strong></a></p><p><strong><em>Docker mac/windows link: </em></strong><a href="https://beta.docker.com/"><strong><em>https://beta.docker.com/</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=542c6c26c59e" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>