<?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: Pemba Sherpa</title>
    <description>The latest articles on DEV Community by Pemba Sherpa (@pembans).</description>
    <link>https://dev.to/pembans</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%2F3124898%2F41876172-ae7c-4aff-ae85-f7552c8a7e92.jpg</url>
      <title>DEV Community: Pemba Sherpa</title>
      <link>https://dev.to/pembans</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pembans"/>
    <language>en</language>
    <item>
      <title>Step-by-Step Guide to Integrate eSewa EPAY v2 with Laravel and Livewire</title>
      <dc:creator>Pemba Sherpa</dc:creator>
      <pubDate>Fri, 27 Jun 2025 01:19:50 +0000</pubDate>
      <link>https://dev.to/pembans/step-by-step-guide-to-integrate-esewa-epay-v2-with-laravel-and-livewire-50bk</link>
      <guid>https://dev.to/pembans/step-by-step-guide-to-integrate-esewa-epay-v2-with-laravel-and-livewire-50bk</guid>
      <description>&lt;p&gt;This blog guides you through integrating eSewa v2 into your Laravel application using Livewire. I assume you already have basic knowledge of Livewire, and that Laravel and Livewire are installed and set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the eSewa Configuration File
&lt;/h2&gt;

&lt;p&gt;First, create a config file (e.g. &lt;code&gt;config/esewa.php&lt;/code&gt;) where you can add your eSewa product code, secret key, production URL, and sandbox URL. Make sure to add these values to your &lt;code&gt;.env&lt;/code&gt; file as well.&lt;br&gt;
&lt;/p&gt;

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

declare(strict_types=1);

return [
    'product_code' =&amp;gt; env('ESEWA_PRODUCT_CODE'),
    'secret' =&amp;gt; env('ESEWA_SECRET'),
    'production_url' =&amp;gt; env('ESEWA_PRODUCTION_URL'),
    'sandbox_url' =&amp;gt; env('ESEWA_SANDBOX_URL'),
];

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Create Your Checkout Component
&lt;/h2&gt;

&lt;p&gt;Assuming you already have a Livewire Checkout Component, you will handle the fields required for the eSewa checkout page inside it. You can name the method anything you like to process this logic; in my case, I used a method called &lt;code&gt;save()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;According to eSewa documentation there are a few important fields to keep in mind such as &lt;code&gt;transaction_uuid&lt;/code&gt; and &lt;code&gt;signature&lt;/code&gt;. The &lt;code&gt;transaction_uuid&lt;/code&gt; needs be unique for every transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Create the Esewa Helper Class (Optional)
&lt;/h2&gt;

&lt;p&gt;To keep your code clean, I created a helper class &lt;code&gt;Esewa.php&lt;/code&gt; that contains reusable logic like fetching credentails and generating signatures.&lt;br&gt;
&lt;/p&gt;

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

declare(strict_types=1);

namespace App\Helpers;

use App\Models\Client;

class Esewa
{
    public static function generateSignature(array $fields): string
    {
        $signatureMessage = collect($fields)-&amp;gt;map(fn ($value, $key): string =&amp;gt; "{$key}={$value}")-&amp;gt;implode(',');

        return base64_encode(hash_hmac('sha256', $signatureMessage, config('esewa.secret'), true));
    }

    public static function getCredentials(): array
    {
        $productCode = config('esewa.product_code');
        $secret = config('esewa.secret');

        return [
            'product_code' =&amp;gt; $productCode,
            'secret' =&amp;gt; $secret,
        ];
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Signature Explanation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;According to eSewa's documentation, to generate the signature, you need to includes these fields along with their values: &lt;code&gt;total_amount&lt;/code&gt;, &lt;code&gt;transaction_uuid&lt;/code&gt;, and &lt;code&gt;product_code&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use App\Helpers\Esewa;

['product_code' =&amp;gt; $productCode,'secret' =&amp;gt; $secret] = Esewa::getCredentials();

$signatureFields = [
  'total_amount' =&amp;gt; $this-&amp;gt;cart-&amp;gt;total,
  'transaction_uuid' =&amp;gt; $transactionUuid,
  'product_code' =&amp;gt; $productCode,
];

$signature = Esewa::generateSignature($signatureFields, $secret);

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

&lt;/div&gt;



&lt;p&gt;In the code, I created an array &lt;code&gt;$signatureFields&lt;/code&gt; containing the required fields for the signature along with their values and passed it to the &lt;code&gt;generateSignature&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The signature message should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total_amount=100,transaction_uuid=128481264,product_code=EPAYTEST

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

&lt;/div&gt;



&lt;p&gt;After that, you need to hash the string using HMAC-SHA256 and then encode it in base64. This process is implemented in the &lt;code&gt;generateSignature&lt;/code&gt; function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Creating the form fields for Esewa Checkout Page
&lt;/h2&gt;

&lt;p&gt;All the required fields are mentioned in the eSewa documentation. Please refer to the official documentation &lt;a href="https://developer.esewa.com.np/pages/Epay#transactionflow" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; $data = [
        'amount' =&amp;gt; $this-&amp;gt;cart-&amp;gt;sub_total,
        'tax_amount' =&amp;gt; $this-&amp;gt;cart-&amp;gt;vat,
        'total_amount' =&amp;gt; $this-&amp;gt;cart-&amp;gt;total,
        'transaction_uuid' =&amp;gt; $transactionUuid,
        'product_code' =&amp;gt; $productCode,
        'product_service_charge' =&amp;gt; $this-&amp;gt;cart-&amp;gt;service_charge,
        'product_delivery_charge' =&amp;gt; 0,
        'success_url' =&amp;gt; route('esewa.success'),
        'failure_url' =&amp;gt; route('esewa.failure'),
        'signed_field_names' =&amp;gt; 'total_amount,transaction_uuid,product_code',
        'signature' =&amp;gt; $signature,
    ];

$this-&amp;gt;dispatch('esewa-form-submit', data: $data);

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Handling the eSewa Form Submission Using JavaScript
&lt;/h2&gt;

&lt;p&gt;eSewa does not support backend checkout directly. Instead, you must create an HTML form with the required fields and submit it to eSewa’s URL. To achieve this, we will use JavaScript to dynamically create and submit the form.&lt;/p&gt;

&lt;p&gt;To avoid exposing JS logic directly in your Blade templates, I created a JavaScript module &lt;code&gt;esewa-form.js&lt;/code&gt; in &lt;code&gt;resources/js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default () =&amp;gt; ({
    formSubmit(data, isProduction = false){
        const form = document.createElement('form');
        form.method = 'POST';
        form.action = isProduction 
            ? 'https://epay.esewa.com.np/api/epay/main/v2/form'
            : 'https://rc-epay.esewa.com.np/api/epay/main/v2/form';

        Object.entries(data).forEach(([key, value]) =&amp;gt; {
            const input = document.createElement('input');
            input.type = 'hidden';
            input.id = key;
            input.name = key;
            input.value = value;
            form.appendChild(input);
        });

        document.body.appendChild(form);
        form.submit();
    }
});

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

&lt;/div&gt;



&lt;p&gt;Register this JS file in your main &lt;code&gt;app.js&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;import './bootstrap';
import esewaForm from './esewa-form';

document.addEventListener('alpine:init', () =&amp;gt; {
    Alpine.data('esewa', esewaForm);
});

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Listen for the Emit Event in Blade
&lt;/h2&gt;

&lt;p&gt;In your &lt;code&gt;checkout.blade.php&lt;/code&gt;, add a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and use Alpine.js to listen for the Livewire &lt;code&gt;esewa-form-submit&lt;/code&gt; event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div x-data="esewa" @esewa-form-submit.window="formSubmit($event.detail.data)"&amp;gt;
  &amp;lt;!-- Your checkout form goes here --&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Don't forget to include Livewire’s &lt;code&gt;@livewireScriptConfig&lt;/code&gt; as explained in the Livewire &lt;a href="https://livewire.laravel.com/docs/installation" rel="noopener noreferrer"&gt;docs&lt;/a&gt;. Without it, it doesn't work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 7: Handling the Payment Success Response
&lt;/h2&gt;

&lt;p&gt;After payment completion, eSewa redirects to your success route with a base64-encoded response string. You need to decode it and verify the signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$transactionResponse = json_decode(base64_decode($request-&amp;gt;data), true);

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

&lt;/div&gt;



&lt;p&gt;A typical response looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "transaction_code": "000AWEO",
  "status": "COMPLETE",
  "total_amount": 1000.0,
  "transaction_uuid": "250610-162413",
  "product_code": "EPAYTEST",
  "signed_field_names": "transaction_code,status,total_amount,transaction_uuid,product_code,signed_field_names",
  "signature": "62GcfZTmVkzhtUeh+QJ1AqiJrjoWWGof3U+eTPTZ7fA="
}

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

&lt;/div&gt;



&lt;p&gt;According to the eSewa documentation, it is necessary to verify the response signature by generating a new signature from the response data and comparing it with the one provided in the response. To verify the response signature, you generate a signature from the response data excluding the &lt;code&gt;signature&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Support\Arr;

$signatureFields = Arr::except($transactionResponse, ['signature']);

$signature = Esewa::generateSignature($signatureFields, config('esewa.secret'));

if ($signature !== $transactionResponse['signature']) {
    // Cancel the transaction due to signature mismatch
    return ;
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 8: Check Payment Status Using eSewa API
&lt;/h2&gt;

&lt;p&gt;Once the signature is verified, it is also necessary to confirm the payment status. To do this, we create an service &lt;code&gt;EsewaService.php&lt;/code&gt; file that calls eSewa’s payment status API.&lt;br&gt;
&lt;/p&gt;

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

declare(strict_types=1);

namespace App\Services;

use Illuminate\Support\Facades\Http;

class EsewaService
{
    public static function paymentStatusCheck(array $data): array
    {
        $url = match (app()-&amp;gt;environment()) {
            'production' =&amp;gt; config('esewa.production_url'),
            default =&amp;gt; config('esewa.sandbox_url')
        } . 'transaction/status/?';

        $urlWithQueryParams = $url . http_build_query([
            'product_code' =&amp;gt; $data['product_code'],
            'total_amount' =&amp;gt; $data['total_amount'],
            'transaction_uuid' =&amp;gt; $data['transaction_uuid'],
        ]);

        $response = Http::get($urlWithQueryParams)-&amp;gt;json();

        return $response;
    }
}

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

&lt;/div&gt;



&lt;p&gt;Call this service after verifying the signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$statusResponse = EsewaService::paymentStatusCheck($transactionResponse);

if ($statusResponse['status'] === 'COMPLETE') {
    // Payment successful, proceed with creating order
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bonus Tip:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to store customer information (like name, email, and contact) when they submit the checkout form, you can use &lt;strong&gt;session&lt;/strong&gt; to store the data and clear it after payment success or failure. &lt;/p&gt;

&lt;p&gt;If you are working with a team, consider using &lt;strong&gt;Data Transfer Objects (DTOs)&lt;/strong&gt; to clearly define response fields with their types.&lt;/p&gt;

&lt;p&gt;If you have any questions or would like me to share a GitHub repo or full working example, feel free to reach out or leave a comment.&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>esewa</category>
      <category>laravel</category>
      <category>livewire</category>
      <category>alpinejs</category>
    </item>
    <item>
      <title>Building React Project with Vite, TailwindCSS, Docker, and Deploying to DigitalOcean</title>
      <dc:creator>Pemba Sherpa</dc:creator>
      <pubDate>Thu, 08 May 2025 07:12:36 +0000</pubDate>
      <link>https://dev.to/pembans/building-react-project-with-vite-tailwindcss-docker-and-deploying-to-digitalocean-3glp</link>
      <guid>https://dev.to/pembans/building-react-project-with-vite-tailwindcss-docker-and-deploying-to-digitalocean-3glp</guid>
      <description>&lt;p&gt;In this tutorial, we will guide through the process of setting up a React Project using Vite as build tool, integrating TailwindCSS for styling, containerizing the application with Docker for both development and production environments, and deploying it to DigitalOcean.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This tutorial assumes you have a basic understanding of Docker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;1. Install React&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, create a new react project using Vite. For installation you can refer to this &lt;a href="https://vite.dev/guide/" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Install TailwindCSS with Vite&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, install the TailwindCSS by following this &lt;a href="https://tailwindcss.com/docs/installation/using-vite" rel="noopener noreferrer"&gt;guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have both React and TailwindCSS set up and working locally, we can process to containerize the application using Docker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Creating a Dockerfile&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will create a &lt;code&gt;Dockerfile&lt;/code&gt; that supports both the development and production environments. To achieve this, we used &lt;strong&gt;multi-stage build&lt;/strong&gt;, which allows us to define multiple &lt;code&gt;FROM&lt;/code&gt; within a single Dockerfile. To learn more about multi-stage builds, click &lt;a href="https://docs.docker.com/build/building/multi-stage/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In our Dockerfile it consists of four stages. They are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base Stage
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:22-alpine3.21 AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci 
COPY . .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by creating a &lt;code&gt;base&lt;/code&gt; stage using &lt;code&gt;AS&lt;/code&gt; keyword. This stage install the exact necessary dependencies from package-lock.json and copies the files from host to container.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dev Stage
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM base AS dev
EXPOSE 5137
CMD ["npm","run","dev"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;dev&lt;/code&gt; stage, we build on top of the &lt;code&gt;base&lt;/code&gt; stage. We then expose port &lt;strong&gt;5137&lt;/strong&gt;, which is the default port for the Vite development server. Finally, we start the development server using &lt;code&gt;npm run dev&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build Stage
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM base AS build
RUN npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this stage, we build on top of the &lt;code&gt;base&lt;/code&gt; stage and execute the &lt;code&gt;npm run build&lt;/code&gt; command to generate optimized static assets for production. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production Stage
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM nginx:1.28.0-alpine AS prod
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx","-g","daemon off;"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;prod&lt;/code&gt; stage, we serve the static files generated by the &lt;code&gt;build&lt;/code&gt; stage. We using &lt;strong&gt;Nginx&lt;/strong&gt;, a lightweight and high-performance web server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;COPY --from=build /app/dist /usr/share/nginx/html&lt;/code&gt;: This command copies the static files from the &lt;code&gt;build&lt;/code&gt; stage (located in &lt;strong&gt;app/dist&lt;/strong&gt;) to Nginx's default directory (&lt;strong&gt;/usr/share/nginx/html&lt;/strong&gt;), making them accessible for serving.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CMD ["nginx","-g","daemon off;"]&lt;/code&gt;: This runs Nginx in the foreground to keep the container running. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;EXPOSE 80&lt;/code&gt;: This command exposes port 80, which is the default port for HTTP traffic and is used by Nginx to serve web content.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, the Dockerfile 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;FROM node:22-alpine-3.21 as base
WORKDIR /app
COPY package*.json .
RUN npm install &amp;amp;&amp;amp; npm install tailwindcss @tailwindcss/vite
COPY . .

FROM base AS dev
EXPOSE 5137
CMD ["npm","run","dev"]

FROM base AS build
RUN npm run build

FROM nginx:1.28.0-alpine as prod
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx","-g","daemon off;"]


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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Creating a Docker Compose&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once, the Dockerfile is created, we create a &lt;strong&gt;docker-compose.dev.yml&lt;/strong&gt;&lt;br&gt;
file to build the image and run the container, simplifying the process of managing services and configurations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.8'
services:
  web:
    build: 
      context: .
      target: dev
    image: react-vite-project:dev
    container_name: react-vite-project-dev
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "5173:5173"
    environment:
      - CHOKIDAR_USEPOLLING=true  

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

&lt;/div&gt;



&lt;p&gt;In the file, we need to specify the target stage &lt;strong&gt;dev&lt;/strong&gt; for our development environment using &lt;code&gt;target:dev&lt;/code&gt;. We then mount the project directory to &lt;code&gt;/app&lt;/code&gt; inside the container to enable live changes, while ignoring the &lt;code&gt;node modules&lt;/code&gt; directory. Additionally, we set the &lt;code&gt;CHOKIDAR_USEPOLLING&lt;/code&gt; environment variable to ensure proper file watching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Vite Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;vite.config.ts&lt;/code&gt; we need to configure the server settings and use polling for file changes to ensure proper-file watching, especially in environments like Docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
  server: {
    host: true,
    strictPort: true,
    port: 5173,
    watch: {
      usePolling: true
    }
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Run Docker Compose&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once everything is configured, you can run the following command:&lt;br&gt;
&lt;code&gt;docker compose -f docker-compose-dev.yml up&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command will build the Docker image and start the container. &lt;br&gt;
You should see similar output in your terminal:&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%2Flmbxxvipr9qzkrcs9o2l.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%2Flmbxxvipr9qzkrcs9o2l.png" alt="Running Docker Compose" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the container is up and running, you can access the application at (&lt;a href="http://localhost:5173" rel="noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. DigitalOcean Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, let's configured the Digital Ocean to deploy our application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup Container Registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, we use DigitalOcean Container Registry to store the private Docker image. To configure the registry, you can follow this &lt;a href="https://docs.digitalocean.com/products/container-registry/getting-started/quickstart/" rel="noopener noreferrer"&gt;instructions&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup App Platform &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After configuring the registry, we set up the App Platform. You can easily create an App Platform from the DigitalOcean dashboard by clicking Create and selecting App Platform.&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%2Fvji51tdwdrlk5m7y70q2.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%2Fvji51tdwdrlk5m7y70q2.png" alt="Creating App Platform" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, when creating the app platform, select the &lt;strong&gt;Container Image&lt;/strong&gt; and choose &lt;strong&gt;DigitalOcean Container Registry&lt;/strong&gt;, since our image is stored there and enable AutoDeploy so that the app automatically updates whenever a new image is pushed to the registry.&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%2Fzxkq497zkbb36a14u83q.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%2Fzxkq497zkbb36a14u83q.png" alt="Selecting App Platform" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Creating GitHub Action&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To build the Docker image and push it to the container registry, we use &lt;strong&gt;GitHub Actions&lt;/strong&gt;. You can create a GitHub Action by going to the &lt;strong&gt;Actions&lt;/strong&gt; tab in your repository and clicking &lt;strong&gt;New Workflow&lt;/strong&gt;. Then, select the &lt;strong&gt;Docker Image&lt;/strong&gt; workflow template to get started.&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%2Fsf9y3y0xmskbm40a4d10.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%2Fsf9y3y0xmskbm40a4d10.png" alt="Creating GitHub Action" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once GitHub Action is created, we need to configure it to work with DigitalOcean Container Registry. &lt;/p&gt;

&lt;p&gt;To authenticate the GitHub Action with DigitalOcean, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a Personal Access Token&lt;/strong&gt;: You can follow &lt;a href="https://docs.digitalocean.com/reference/api/create-personal-access-token/" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; to create a token from your DigitalOcean account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In your &lt;strong&gt;GitHub repository&lt;/strong&gt;, go to &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Secrets and variables&lt;/strong&gt; → &lt;strong&gt;Actions&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following secrets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DO_USERNAME&lt;/code&gt;: The token name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DO_ACCESS_TOKEN&lt;/code&gt;: The token value.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Docker Image CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:

  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Log in to DigitalOcean Container Registry
      uses: docker/login-action@v3
      with:
        registry: registry.digitalocean.com
        username: ${{secrets.DO_USERNAME}}
        password: ${{secrets.DO_ACCESS_TOKEN}}

    - name: Build the Docker image
      run: |
        docker build . --file Dockerfile --target prod --tag react-vite-project:latest 
        docker tag react-vite-project:latest registry.digitalocean.com/private-docker-images/react-vite-project:latest

    - name: Push Docker Image to Digital Ocean Registry
      run: |
        docker push registry.digitalocean.com/private-docker-images/react-vite-project:latest

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

&lt;/div&gt;



&lt;p&gt;In the GitHub Action, we need to specify the &lt;code&gt;prod&lt;/code&gt; target and tag the image so it can be pushed to the DigitalOcean Container Registry. Finally, we push the image to the registry.&lt;/p&gt;

&lt;p&gt;Once the image is pushed to the registry, you can view it in the &lt;strong&gt;Container Registry&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flujn9ya7k87pxomzostj.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%2Flujn9ya7k87pxomzostj.png" alt="Container Registry" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the image is pushed, it is automatically deployed on App Platform. You can click the link above to view it.&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%2Feutcuzip8en4hxqms1q7.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%2Feutcuzip8en4hxqms1q7.png" alt="App Platform" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In this guide, we walked through setting up a React, TailwindCSS application using Vite, containerizing it with Docker, automating the build and deployment process with GitHub Actions, and deploying it using DigitalOcean App Platform. &lt;/p&gt;

&lt;p&gt;If you found this helpful, feel free to share it or leave a comment. Happy coding!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>react</category>
      <category>tailwindcss</category>
      <category>vite</category>
    </item>
  </channel>
</rss>
