Table of contents
- Maintaining code standards
- Hosting: AWS S3 + CloudFront vs Netlify/Render/Cloudflare
- S3 + CloudFront (Static Hosting + CDN + SSL)
- Alternatives (Cloudflare Pages / Netlify / Render)
- Multi-environment deployment
- CI — GitHub Actions: Automated & Manual Workflows
- Auto-Deploy by Branch Name
- Manual Deploys with workflow\dispatch
- CD — Deploy to AWS (S3 + CloudFront)
- 1. Set up AWS IAM OIDC Provider
- 2. Create an IAM Role for the Github Actions to assume
- 3. Attach Policies to the Role
- Update the application with aws sync
- Branch security measures
- Final github workflow
- Notify your team on deployment
- Final Thoughts
You're launching an MVP and want to deploy you React app with minimal overhead, while limiting costs? In this tutorial, you’ll learn how to deploy a multi-environment React app to AWS S3.
Maintaining code standards
"If you want to go fast, if you want to get done quickly, if you want your code to be easy to write, make it easy to read" Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship
To make sure your app is maintainable, it must be easy to read, which makes it easy to continue its development.
- Configure your IDE for linting and auto-formatting
In order to maintain styling standards during development, we can install the following extensions:
- Tailwind CSS IntelliSense, which provides intellisense for tailwind classes.
- ESLint extension and Prettier - Code formatter.
In VS Code settings (CTRL + ,):
- Update "Editor: default formatter" to "Prettier - Code formatter".
- Enable "Editor: Format on Save"
- Use Domain-Driven Design (DDD) + Clean Structure
Instead of dumping all components into a components/ folder and praying for clarity, we organize code by business feature, not technical and overly generic type.
Each business feature should have its own dedicated folder containing its components, hooks, etc, forming what we call a bounded context.
What is a bounded context in DDD? Imagine a user model. In the Auth feature, the model is an email/password pair. In the Admin feature, it's actually roles/permissions. Those business objects have the same name, but different responsibilities. So we isolate them in bounded contexts!
A bounded contextdefines the boundaries of your feature.
Project Structure
- src/features: One folder per business capability (e.g., auth, theme, dashboard)
- src/core: Reusable, app-agnostic components (e.g., Button, Layout, useDebounce)
- src/shared: Project-wide reusable but project-specific logic/components
- src/pages: Each page (e.g. home, 404, etc)
- src/assets: Global images/icons
- Best practices when using DDD
- Group files by feature
Each feature manages its own components and states, including its own Zustand slices under
features/X/store/X.slice.tsif you use Zustand. - Expose a minimal public API with a
barrel exportEach feature should have anìndex.tsfile that exports everything a feature exposes - Keep
core/generic Don't leak feature logic intocore/ - Shared = Project-Specific Utilities
- What are DDD anti-patterns?
- Don't put business logic in
core/ - Do not use global stores in
src/stores/for state management, this breaks the DDD logic. - Don't overt-abstract The more flat the architecture is, the better. Keep It Simple, Stupid (KISS)
- Special cases: Use Open/Closed principle
OCPfromSOLID
Bonus: Open/Closed Principle for Reusability
In features/theme/components, create a generic ThemeToggleButton, then create a ConnectedThemeToggle that plugs into Zustand or Redux. Boom – portable and extendable.
ThemeToggleButton is a closed, reusable component that takes callbacks.
Hosting: AWS S3 + CloudFront vs Netlify/Render/Cloudflare
A React app is just static files (HTML/CSS/JS), so why not host it on S3 for cents/month?
The dynamic content displayed in your website is fetched on client side when the user loads the page. That means you can just build your website and host the files on a static site hosting solution such as a quasi-free S3 bucket (HTTP).
S3 + CloudFront (Static Hosting + CDN + SSL)
- Buy a domain name on a domain name registrar such as porkbun, Namecheap, etc. I recommend you to compare domain names prices by going to https://tld-list.com/
- Delegate DNS to AWS Route53 or Cloudflare.
DNS recordsare instructions that state how to handle request and associate IP addresses to the domain.You can use AWS Route 53 hosted zone for 0.50€/month, or Cloudflare, which provides DNS management and a web application firewall for free. There will however me more DNS configuration steps to serve the CloudFront distributions.The DNS provider of your choice will ask you to register theirnameserversin your domain name registrar DNS settings. - Set up SSL via AWS Certificate Manager
- Serve via CloudFront (CDN + HTTPS + cache)
- Upload static build to S3 bucket
Alternatives (Cloudflare Pages / Netlify / Render)
They offer:
- Git-based CI/CD
- Easy custom domains
- Serverless functions (like Cloudflare Workers)
- Great free tiers for hobby projects
Which one to use? Depends on your needs and what each platform's ecosystem can provide:
- Cloudflare Pages is arguably the best free-tier platform for static sites and Workers, which are serverless functions that always run at the edge, making them incredibly fast.
- Firebase , made by Google, is great if you want backend capabilities like database and auth.
Disclaimer: Firebase Authentication free tier (Spark Plan) has strong limitations when it comes to phone auth (limited to 10k verifications/month in the US, or to ~100-300 verifications/month in other countries) or to MFA, which is just plain unavailable.For authentication, there's not even to hesitate – Start with Supabase Cloud for a quick setup and a generous free tier (50k
Montly Active Users (MAU)+ $8/mo for 100k MAU). Then if needed, forget the subscription and self-host Supabase which is open-source. Since march 2025 Supabase introduced the Supabase UI Library base on tailwind CSS and ShadCN, which was the only missing element from this wholesome solution. - Render is strong if you need both static and backend services in one place.
Multi-environment deployment
When your web application starts having lots of traffic, you need to make a pre-release to ensure the changes do not break production. Real projects need:
- dev: unstable, local testing
- qa: for internal team testing
- stg: user testing / UAT
- prod: customer-facing production
Your configuration will usually vary between environments:
- Different API URLs: When relying on APIs, The staging & dev environment will use the sandboxed version of the APIs.
- Different feature toggles: You can use feature flags to disable a feature for various reasons: 1) A new feature is under active development and has been merged into the main codebase for agility's sake. 2) A published feature doesn't meet the customers' standards and has been disabled to be reworked.
We'll bake the configuration at build time. This means we build the app once per environment.
Vite variable support
Vite exposes env variables under import.meta.env object as strings automatically. These variables must be prefixed with VITE_ in .env files. We can use them like so:
<span <span class="hljs-keyword">class</span>=<span class="hljs-string">"hljs-variable language_"</span>><span class="hljs-variable language_">console</span><<span class="hljs-regexp">/span>.<span class="hljs-title function_">log</</span>span>(<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-keyword"</span>></span>import<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>.<span <span class="hljs-keyword">class</span>=<span class="hljs-string">"hljs-property"</span>>meta<<span class="hljs-regexp">/span>.<span class="hljs-property">env</</span>span>.<span <span class="hljs-keyword">class</span>=<span class="hljs-string">"hljs-property"</span>><span class="hljs-variable constant_">VITE_API_ENDPOINT</span></span>);
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-comment"</span>></span>// resolves to <span class="hljs-symbol">&quot;</span>http://localhost:8080/<span class="hljs-symbol">&quot;</span><span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>Built-in Constants
By default, when running vite, NODE_ENV is set to developmentand .env.development is used.
When running vite build, NODE_ENV is set to productionand .env.production is used.
Vite provides the following built-in constants:
import.meta.env.PROD: true if NODE_ENV equals to productionimport.meta.env.DEV: true if NODE_ENV equals to development (always the opposite ofimport.meta.env.PROD)import.meta.env.SSR: whether the app is running in the server.
Another concept from Vite is the Mode, which takes by default the same values ad NODE_ENV. You can overwrite the default mode by passing the --mode and retrieve it using import.meta.env.MODE.
If you want to build your app for a staging mode, create a .env.staging and run vite build --mode staging.
Environment configurations
For our setup, define the following .env.[mode] files:
- .env.development:
VITE_API_URI="http://localhost:8080/" - .env.qa:
VITE_API_URI="https://api-qa.example.com/" - .env.stg:
VITE_API_URI="https://api-staging.example.com/" - .env.production
VITE_API_URI="https://api.example.com/"Also add the lineVITE_APP_VERSION=$npm_package_versionto each .env files. This makes the app version specified inpackage.jsonaccessible in the whole app throughimport.meta.env.VITE_APP_VERSION.
Add type definitions for custom env variables in src/vite-environment.d.ts to augment ImportMetaEnv:
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-comment"</span>></span>/// <span class="hljs-symbol">&lt;</span>reference types=<span class="hljs-symbol">&quot;</span>vite/client<span class="hljs-symbol">&quot;</span> /<span class="hljs-symbol">&gt;</span><span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-comment"</span>></span>/// <span class="hljs-symbol">&lt;</span>reference types=<span class="hljs-symbol">&quot;</span>vite/types/importMeta.d.ts<span class="hljs-symbol">&quot;</span> /<span class="hljs-symbol">&gt;</span><span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-comment"</span>></span>/// <span class="hljs-symbol">&lt;</span>reference types=<span class="hljs-symbol">&quot;</span>vite-plugin-svgr/client<span class="hljs-symbol">&quot;</span> /<span class="hljs-symbol">&gt;</span><span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-comment"</span>></span>/// <span class="hljs-symbol">&lt;</span>reference types=<span class="hljs-symbol">&quot;</span>vite/client<span class="hljs-symbol">&quot;</span> /<span class="hljs-symbol">&gt;</span><span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-keyword"</span>></span>interface<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-title class_"</span>></span>ViteTypeOptions<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span> {
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-comment"</span>></span>// By adding this line, you can make the type of ImportMetaEnv strict<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-comment"</span>></span>// to disallow unknown keys.<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-attr"</span>></span>strictImportMetaEnv<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>: <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-built_in"</span>></span>unknown<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
}
<span <span class="hljs-keyword">class</span>=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// eslint-disable-next-line unicorn/prevent-abbreviations</span></span>
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-keyword"</span>></span>interface<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-title class_"</span>></span>ImportMetaEnv<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span> {
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-keyword"</span>></span>readonly<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-attr"</span>></span>VITE_APP_VERSION<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>: <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-built_in"</span>></span>string<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-keyword"</span>></span>readonly<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-variable constant_"</span>></span>VITE_API_URI<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>?: <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-built_in"</span>></span>string<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-comment"</span>></span>// more env variables...<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
}
<span <span class="hljs-keyword">class</span>=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">interface</span></span> <span class="hljs-title class_"><span class="hljs-title class_">ImportMeta</span></span> {
<span <span class="hljs-keyword">class</span>=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">readonly</span><<span class="hljs-regexp">/span> <span class="hljs-attr">env</</span>span>: <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hljs-title class_"</span>></span>ImportMetaEnvironment<span class="hljs-tag"></<span class="hljs-name">span</span>></span></span>
}In package.json, define one build command per environment:
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;scripts&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;start&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vite&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;lint&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;eslint .&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;test&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vitest run&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;build&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vite build&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;build<span class="hljs-punctuation">:</span>prod&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vite build&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;build<span class="hljs-punctuation">:</span>stg&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vite build --mode stg&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;build<span class="hljs-punctuation">:</span>qa&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vite build --mode qa&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>CI — GitHub Actions: Automated & Manual Workflows
Auto-Deploy by Branch Name
| Source Branch | Environment |
|---|---|
main |
production |
staging |
staging |
qa |
qa |
release/* |
qa |
feature/* |
qa |
bugfix/* |
qa |
Listen to Github events by creating a workflow (.github/workflows/deploy.yml):
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">on:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">push:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">branches:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;main&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;staging&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;qa&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;release/*&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;feature/*&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;bugfix/*&#x27;</span></span>The push event is triggered on direct push to branch and when a PR is merged into branch.
Compute the target mode based on Github workflow github.* context:
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">env:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">MODE:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">(github.ref_name</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">==</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;main&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&amp;&amp;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;prod&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">||</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">github.ref_name</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">==</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;staging&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&amp;&amp;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;stg&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">||</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;qa&#x27;</span><span</span> <span class="hljs-string">class="hljs-string">)</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>The environment variable ${{ env.MODE }} takes the values 'prod' | 'staging' | 'qa'.
In the build stage, reference that environment variable:
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Build</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">project</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">run:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">npm</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">run</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">build:${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">env.MODE</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>Finally we upload the built React app artifact, so that we can re-use it in the deployment job:
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Upload</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">build</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">artifacts</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">actions/upload-artifact@v4</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">with:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">web-dist</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">path:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">./dist</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">retention-days:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-number">1</span></span>Manual Deploys with workflow_dispatch
Trigger deploys manually and select the environment from the GitHub UI.
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">on:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">workflow_dispatch:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">inputs:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">environment:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">type:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">choice</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">options:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">prod</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">stg</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">qa</span></span>Usually, multiple features are being developped at the same time. This means not everyone can push their single under development feature on the staging branch. To easily let developers deploy to that environment, we'll use workflow_disatch.
First, we need to listen the workflow_dispatch event:
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">on:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">workflow_dispatch:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">inputs:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">environment:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">description:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;Select</span> <span class="hljs-string">the</span> <span class="hljs-string">environment</span> <span class="hljs-string">to</span> <span class="hljs-string">deploy</span> <span class="hljs-string">to&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">required:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-literal">true</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">default:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;qa&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">type:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">choice</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">options:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">qa</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">staging</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">production</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">push:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">branches:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;main&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;staging&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;qa&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;release/*&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;feature/*&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;bugfix/*&#x27;</span></span>Modify the target deployment environment specifically when the source event is workflow_dispatch:
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">env:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">ENVIRONMENT:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">github.event_name</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">==</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;workflow_dispatch&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&amp;&amp;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">github.event.inputs.environment</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">||</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">(github.ref_name</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">==</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;main&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&amp;&amp;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;production&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">||</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">github.ref_name</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">==</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;staging&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&amp;&amp;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;staging&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">||</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;qa&#x27;</span><span</span> <span class="hljs-string">class="hljs-string">)</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>In your repo's Actions you can select the target environment to deploy to under Run workflow.
CD — Deploy to AWS (S3 + CloudFront)
We're now able to build our application with the target environment's configuration both in an automated and manual way with workflow dispatch, ensuring flexibility for our development team.
We'll host each environment in a separate S3 bucket to ensure isolation. In this tutorial we will assume that the S3 buckets already exist. The pipeline will only upload the newer app's distributable. All buckets should only be accessible through CloudFront to enable caching, so you'll have one CloudFront distribution per environment.
As the website is only static, a developer could deploy to the target environment from its computer by using the following scripts in package.json:
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
...
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;scripts&quot;</span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;build<span class="hljs-punctuation">:</span>prod&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vite build --mode prod&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;build<span class="hljs-punctuation">:</span>stg&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vite build --mode stg&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;build<span class="hljs-punctuation">:</span>dev&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vite build --mode dev&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;build&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;vite build --mode prod&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;sync<span class="hljs-punctuation">:</span>prod&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;aws s3 sync dist s3<span class="hljs-punctuation">:</span><span class="hljs-comment">//prod-launchplate-react-primary &amp;&amp; aws cloudfront create-invalidation --distribution-id XXXX1 --paths &#x27;/*&#x27;&quot;</span><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;sync<span class="hljs-punctuation">:</span>stg&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;aws s3 sync dist s3<span class="hljs-punctuation">:</span><span class="hljs-comment">//stg-launchplate-react-primary &amp;&amp; aws cloudfront create-invalidation --distribution-id XXXX2 --paths &#x27;/*&#x27;&quot;</span><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;sync<span class="hljs-punctuation">:</span>dev&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;aws s3 sync dist s3<span class="hljs-punctuation">:</span><span class="hljs-comment">//dev-launchplate-react-primary &amp;&amp; aws cloudfront create-invalidation --distribution-id XXXX3 --paths &#x27;/*&#x27;&quot;</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>Please replace the values with your buckets names and cloudfront distribution id.
We want the Github Workflow to perform these S3 synchronizations for us. To enable the workflow to communicate with AWS, we can leverage AWS OIDC Authentication, which doesn't require any exchange of secrets.
1. Set up AWS IAM OIDC Provider
<span class=<span class="hljs-string">"hljs-comment"</span>># Create the OIDC provider <span class="hljs-keyword">in</span> AWS</span>
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list <span class=<span class="hljs-string">"hljs-string"</span>>&quot;a031c46782e6e6c662c2c87c76da9aa62ccabd8e&quot;</span>You get the following output:
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;OpenIDConnectProviderArn&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;arn<span class="hljs-punctuation">:</span>aws<span class="hljs-punctuation">:</span>iam<span class="hljs-punctuation">:</span><span class="hljs-punctuation">:</span>AWS_ACCOUNT_ID<span class="hljs-punctuation">:</span>oidc-provider/token.actions.githubusercontent.com&quot;</span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>2. Create an IAM Role for the Github Actions to assume
Create an IAM role named ReactLaunchplateGitHubActionsOIDCRole with the following trust policy:
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Version&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;<span class="hljs-number">2012</span><span class="hljs-number">-10</span><span class="hljs-number">-17</span>&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Statement&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">[</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Effect&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;Allow&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Principal&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Federated&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;arn<span class="hljs-punctuation">:</span>aws<span class="hljs-punctuation">:</span>iam<span class="hljs-punctuation">:</span><span class="hljs-punctuation">:</span>&lt;AWS_ACCOUNT_ID&gt;<span class="hljs-punctuation">:</span>oidc-provider/token.actions.githubusercontent.com&quot;</span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Action&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;sts<span class="hljs-punctuation">:</span>AssumeRoleWithWebIdentity&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Condition&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;StringEquals&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;token.actions.githubusercontent.com<span class="hljs-punctuation">:</span>aud&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;sts.amazonaws.com&quot;</span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;StringLike&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;token.actions.githubusercontent.com<span class="hljs-punctuation">:</span>sub&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;repo<span class="hljs-punctuation">:</span>&lt;GITHUB_ORG&gt;/&lt;REPO_NAME&gt;<span class="hljs-punctuation">:</span>*&quot;</span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">]</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>Replace <AWS_ACCOUNT_ID> with your AWS account ID, retrieved from the previous step.
Replace <GITHUB_ORG> and <REPO_NAME> with your GitHub organization and repository names.
You can also create the role using aws CLI. Create a file oidc-provider-role.json containing the previous JSON, then run:
cd docs
aws iam create-role --role-name ReactLaunchplateGitHubActionsOIDCRole --assume-role-policy-document file://oidc-provider-role.jsonAs an output, we get:
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Role&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Path&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;/&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;RoleName&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;ReactLaunchplateGitHubActionsOIDCRole&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Arn&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;arn<span class="hljs-punctuation">:</span>aws<span class="hljs-punctuation">:</span>iam<span class="hljs-punctuation">:</span><span class="hljs-punctuation">:</span>&lt;AWS_ACCOUNT_ID&gt;<span class="hljs-punctuation">:</span>role/ReactLaunchplateGitHubActionsOIDCRole&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
...
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>3. Attach Policies to the Role
We need to attach the necessary policies to this role (e.g., S3FullAccess, CloudFrontFullAccess, Route53FullAccess, etc.)
First, we need to create the policy.
Create a file named provider-policy.json with the following content:
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Version&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;<span class="hljs-number">2012</span><span class="hljs-number">-10</span><span class="hljs-number">-17</span>&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Statement&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">[</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Effect&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;Allow&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Action&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">[</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;s3<span class="hljs-punctuation">:</span>*&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;dynamodb<span class="hljs-punctuation">:</span>GetItem&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;dynamodb<span class="hljs-punctuation">:</span>PutItem&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;dynamodb<span class="hljs-punctuation">:</span>DeleteItem&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;dynamodb<span class="hljs-punctuation">:</span>UpdateItem&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;cloudfront<span class="hljs-punctuation">:</span>*&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;acm<span class="hljs-punctuation">:</span>DescribeCertificate&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;acm<span class="hljs-punctuation">:</span>ListCertificates&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;acm<span class="hljs-punctuation">:</span>ListTagsForCertificate&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;acm<span class="hljs-punctuation">:</span>GetCertificate&quot;</span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">]</span></span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Resource&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">[</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;arn<span class="hljs-punctuation">:</span>aws<span class="hljs-punctuation">:</span>s3<span class="hljs-punctuation">:</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">:</span>&lt;PROJECT_NAME&gt;-tfstate&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-string"</span>>&quot;arn<span class="hljs-punctuation">:</span>aws<span class="hljs-punctuation">:</span>s3<span class="hljs-punctuation">:</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">:</span>&lt;PROJECT_NAME&gt;-tfstate<span class="hljs-comment">/*&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:dynamodb:eu-west-3:YOUR_AWS_ACCOUNT_ID:table/&lt;PROJECT_NAME&gt;-tfstate-locks&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:s3:::*-&lt;PROJECT_NAME&gt;-primary&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:s3:::*-&lt;PROJECT_NAME&gt;-primary/*&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:s3:::*-&lt;PROJECT_NAME&gt;-failover&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:s3:::*-&lt;PROJECT_NAME&gt;-failover/*&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:s3:::*-&lt;PROJECT_NAME&gt;-log&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:s3:::*-&lt;PROJECT_NAME&gt;-log/*&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:cloudfront::&lt;AWS_ACCOUNT_ID&gt;:distribution/*&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:acm:us-east-1:&lt;AWS_ACCOUNT_ID&gt;:certificate/*&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;arn:aws:cloudfront::***:origin-access-identity/*&quot;</span>
<span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-punctuation">{</span>
<span class="hljs-attr">&quot;Effect&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Allow&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">&quot;Action&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
<span class="hljs-string">&quot;s3:ListAllMyBuckets&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;acm:ListCertificates&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-string">&quot;cloudfront:ListDistributions&quot;</span>
<span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">&quot;Resource&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;*&quot;</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-punctuation">{</span>
<span class="hljs-attr">&quot;Effect&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Allow&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">&quot;Action&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;route53:GetHostedZone&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;route53:ListResourceRecordSets&quot;</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">&quot;Resource&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;arn:aws:route53:::hostedzone/*&quot;</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-punctuation">{</span>
<span class="hljs-attr">&quot;Effect&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Allow&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">&quot;Action&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;route53:ChangeResourceRecordSets&quot;</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">&quot;Resource&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;arn:aws:route53:::hostedzone/*&quot;</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-punctuation">{</span>
<span class="hljs-attr">&quot;Effect&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Allow&quot;</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">&quot;Action&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;route53:ListHostedZones&quot;</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">&quot;Resource&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;*&quot;</span>
<span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</span>Modify oidc-provider-policy.json by replacing <PROJECT_NAME> with your project name (e.g., launchplate-react) and <AWS_ACCOUNT_ID> with your AWS account ID. Then run:
aws iam create-policy --policy-name ReactLaunchplateGitHubActionsOIDCRolePolicy --policy-document file://oidc-provider-policy.jsonYou get the following output:
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Policy&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">{</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;PolicyName&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;ReactLaunchplateGitHubActionsOIDCRolePolicy&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
<span class=<span class="hljs-string">"hljs-attr"</span>>&quot;Arn&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">:</span></span> <span class=<span class="hljs-string">"hljs-string"</span>>&quot;arn<span class="hljs-punctuation">:</span>aws<span class="hljs-punctuation">:</span>iam<span class="hljs-punctuation">:</span><span class="hljs-punctuation">:</span>&lt;AWS_ACCOUNT_ID&gt;<span class="hljs-punctuation">:</span>policy/ReactLaunchplateGitHubActionsOIDCRolePolicy&quot;</span><span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">,</span></span>
...
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>
<span class=<span class="hljs-string">"hljs-punctuation"</span>><span class="hljs-punctuation">}</span></span>Now than the policy is created, we can attach it to the role:
aws iam attach-role-policy --role-name ReactLaunchplateGitHubActionsOIDCRole --policy-arn arn:aws:iam::&lt;AWS_ACCOUNT_ID&gt;:policy/ReactLaunchplateGitHubActionsOIDCRolePolicyUpdate the application with aws sync
The Github workflow authenticates to AWS usin the action aws-actions/configure-aws-credentials@v2 to which we pass the ARN of the role to assume, as well as the targeted AWS region.
In your repository's secrets, set AWS_ROLE_ARN to arn:aws:iam::<AWS_ACCOUNT_ID>:role/ReactLaunchplateGitHubActionsOIDCRole.
Add this job to the workflow:
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">env:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">aws_region:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">eu-west-3</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">jobs:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">...</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">deploy:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Web</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Sync</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">to</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">S3</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">needs:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">build</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">runs-on:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">ubuntu-latest</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">steps:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Checkout</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">repository</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">actions/checkout@v4</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Setup</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">AWS</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">credentials</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">aws-actions/configure-aws-credentials@v2</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">with:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">role-to-assume:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">secrets.AWS_ROLE_ARN</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">aws-region:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">env.aws_region</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Download</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">build</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">artifacts</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">actions/download-artifact@v4</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">with:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">web-dist</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">path:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">./dist</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Sync</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">to</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">S3</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">and</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">invalidate</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">CloudFront</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">cache</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">run:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">npm</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">run</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">sync:${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">env.MODE</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>Your website is now automatically synced on S3, and the cache is invalidated to make the changes instantly visible.
Branch security measures
To ensure the review process is followed, you should disallow pushing to the main branch by going to Settings > Rules > New branch ruleset.
- Ruleset Name: Branch proteciton rules
- Targets: Add target > Include default branch
- Enable the following rules:
- Restrict deletions
- Require a pull request before merging. I do not recommend adding required approvals, as it makes the whole pull request process slower. I like the way Gitlab let us set approvals as required while allowing self-approval; The process of bypassing approvals become conscious and make a step back; Does my PR requires improvements or should I approve it already?
You can add a bypass setting to let certain roles bypass the ruleset:
You can also bypass force push by going to Classic Branch protection rules > Branches > Add rule.
- Set branch name pattern to
* - Check "Require a pull request before merging"
- Check "Require approvals" to 1 approval.
- Under Rules applied to everyone including administrators, check Allow force pushes, then select Specify who can force push.
Final github workflow
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">AWS</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Deployment</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">on:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">workflow_dispatch:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">inputs:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">environment:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">description:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;Select</span> <span class="hljs-string">the</span> <span class="hljs-string">environment</span> <span class="hljs-string">to</span> <span class="hljs-string">deploy</span> <span class="hljs-string">to&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">required:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-literal">true</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">default:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;qa&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">type:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">choice</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">options:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">qa</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">stg</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">prod</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">push:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">branches:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;main&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;staging&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;qa&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;release/*&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;feature/*&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;bugfix/*&#x27;</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">env:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">MODE:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">github.event_name</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">==</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;workflow_dispatch&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&amp;&amp;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">github.event.inputs.environment</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">||</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">(github.ref_name</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">==</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;main&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&amp;&amp;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;prod&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">||</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">github.ref_name</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">==</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;staging&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&amp;&amp;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;stg&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">||</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;qa&#x27;</span><span</span> <span class="hljs-string">class="hljs-string">)</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">jobs:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">build:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Lint,</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Test</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&amp;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Build</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">runs-on:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">ubuntu-latest</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">outputs:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">app_version:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">steps.get_version.outputs.app_version</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">steps:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Checkout</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">repository</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">actions/checkout@v4</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Setup</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Node</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">actions/setup-node@v4</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">with:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">node-version:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">&#x27;20&#x27;</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-comment">#</span> <span class="hljs-string">specify</span> <span class="hljs-string">a</span> <span class="hljs-string">version</span> <span class="hljs-string">if</span> <span class="hljs-string">needed</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Setup</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">pnpm</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">pnpm/action-setup@v4.1.0</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">with:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">version:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">latest</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">run_install:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-literal">true</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Run</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Vitest</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">tests</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">run:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">pnpm</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">test</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">env:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">CI:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-literal">true</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Get</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">App</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">version</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">id:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">get_version</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">run:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">|</span>
<span class="hljs-string">APP_VERSION=$(node</span> <span class="hljs-string">-p</span> <span class="hljs-string">&quot;require(&#x27;./package.json&#x27;).version&quot;)</span>
<span class="hljs-string">echo</span> <span class="hljs-string">&quot;app_version=$APP_VERSION&quot;</span> <span class="hljs-string">&gt;&gt;</span> <span class="hljs-string">$GITHUB_OUTPUT</span>
<span class="hljs-string">echo</span> <span class="hljs-string">&quot;App</span> <span class="hljs-attr">Version:</span> <span class="hljs-string">$APP_VERSION&quot;</span>
<span class="hljs-string"></span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Build</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">project</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">run:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">npm</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">run</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">build:${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">env.MODE</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Upload</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">production-ready</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">build</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">files</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">id:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">deployment</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">actions/upload-pages-artifact@v3</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">with:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">path:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">./dist</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">deploy:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Web</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Sync</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">to</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">S3</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">needs:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">build</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">runs-on:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">ubuntu-latest</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">steps:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Checkout</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">repository</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">actions/checkout@v4</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Setup</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">AWS</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">credentials</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">aws-actions/configure-aws-credentials@v2</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">with:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">role-to-assume:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">secrets.AWS_ROLE_ARN</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">aws-region:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">env.aws_region</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Download</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">build</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">artifacts</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">actions/download-artifact@v4</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">with:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">web-dist</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">path:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">./dist</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Sync</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">to</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">S3</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">and</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">invalidate</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">CloudFront</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">cache</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">run:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">npm</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">run</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">sync:${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">env.MODE</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>Notify your team on deployment
I find it really useful to get notified on Slack at the end of the workflow:
- I instantly know when the Github Action has ended and can go check the results.
- This confirms other team members that a new version has been deployed.
You can get Slack notifications by creating a Slack App that has an incoming webhook. To do that please check out Slack's tutorial.
Once done, you can add the following job to your Github workflow:
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">slack:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">name:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">Slack</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">needs:</span></span> [<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">build</span></span>, <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">deploy</span></span>]
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">runs-on:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">ubuntu-latest</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">if:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">always()</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">steps:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">technote-space/workflow-conclusion-action@v3</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-bullet">-</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">uses:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">8398a7/action-slack@v3</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">with:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">status:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">custom</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">fields:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">workflow,job,commit,repo,ref,author,took</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">custom_payload:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">|</span>
{
<span class="hljs-attr">attachments:</span> [{
<span class="hljs-attr">color:</span> <span class="hljs-string">&#x27;$<span class="hljs-template-variable">{{ env.WORKFLOW_CONCLUSION }}</span></span><span class="hljs-string">&#x27;</span> <span class="hljs-string">===</span> <span class="hljs-string">&#x27;success&#x27;</span> <span class="hljs-string">?</span> <span class="hljs-string">&#x27;good&#x27;</span> <span class="hljs-string">:</span> <span class="hljs-string">&#x27;$<span class="hljs-template-variable">{{ env.WORKFLOW_CONCLUSION }}</span></span><span class="hljs-string">&#x27;</span> <span class="hljs-string">===</span> <span class="hljs-string">&#x27;failure&#x27;</span> <span class="hljs-string">?</span> <span class="hljs-string">&#x27;danger&#x27;</span> <span class="hljs-string">:</span> <span class="hljs-string">&#x27;warning&#x27;</span>,
<span class="hljs-attr">text:</span> <span class="hljs-string">`$</span>{<span class="hljs-string">process.env.AS_WORKFLOW</span>} <span class="hljs-string">—</span> <span class="hljs-string">*$<span class="hljs-template-variable">{{ env.WORKFLOW_CONCLUSION }}</span></span><span class="hljs-string">*</span>
<span class="hljs-string">$</span>{<span class="hljs-string">process.env.AS_AUTHOR</span>} <span class="hljs-string">deployed</span> <span class="hljs-string">$</span>{<span class="hljs-string">process.env.AS_REPO</span>}<span class="hljs-string">@$</span>{<span class="hljs-string">process.env.AS_REF</span>} <span class="hljs-string">($</span>{<span class="hljs-string">process.env.AS_COMMIT</span>}<span class="hljs-string">)</span> <span class="hljs-string">in</span> <span class="hljs-string">$</span>{<span class="hljs-string">process.env.AS_TOOK</span>}
<span class="hljs-string"></span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">*Environment:*</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">$<span class="hljs-template-variable">{{</span> <span class="hljs-string">needs.build.outputs.app_version</span> <span class="hljs-string">}}</span></span><span class="hljs-string">`</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string"></span>}]<span class="hljs-string"></span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string"></span>}<span class="hljs-string"></span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">env:</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">GITHUB_TOKEN:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">secrets.GITHUB_TOKEN</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>
<span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-attr">SLACK_WEBHOOK_URL:</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">${{</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">secrets.SLACK_WEBHOOK_URL</span></span> <span class="hljs-string"><span</span> <span class="hljs-string">class="hljs-string">}}</span></span>Only set the slack webhook url in your Github repo's secrets, as secrets.GITHUB_TOKEN is automatically set.
Final Thoughts
You now have:
- A modern React project template
- DDD architecture best practices
- CI/CD for multi-env deploys
- S3 + CloudFront static site hosting
- Peace of mind and bragging rights
- Want to skip the boilerplate? Just clone the repo: launchplate-react-terraform ;)
More Articles

Create your own MCP servers
Discover how to design and build secure MCP servers using Typescript or Python.

MCP servers - Introduction & Guide
In this article, I explain how MCPs (Model Context Protocols) work and how to integrate them into your IDEs to connect external tools to your agent, providing it with context and levers for action to reduce redundant tasks -> Your IDE becomes a true all-in-one tool.

Cloud Email Microservices: A Guide to Using AWS Lambda and Cloudflare Workers
Deploy an email microservice on Lambda and handle queues — invoke from Cloudflare Workers or any Node.js backend
remix.run.png)
Best Practices for an Optimized Contact Page Design
Build a Contact Page That Connects — and Blocks Spam
.png)
7 Ways to Stop Form Spam in Remix / Node.js
Flag bot activity, use built-in rate limit APIs, prevent bounced emails
Remix.png)
Send and Receive Custom Domain Emails for Free
Set up free professional email addresses like you@yourdomain.com without hosting a mail server or paying for Google Workspace. This guide shows how to receive emails using Forward Email and send as your custom domain via Gmail — fast, reliable, and 100% free.
.png)

Comments
Be the first to comment!