Deploy a Bun + Elysia App to AWS EC2
Deploy a food randomizer app to a live Ubuntu server with Nginx and PM2.
Introduction
30 Second Summary
You built a cool app that picks random food for you. But right now it only works on your laptop. What if you could send a link to a friend and they could use it from their phone, anywhere in the world?
In this project, you will deploy your "วันนี้กินไร" app to a real Linux server on AWS, configure it to stay running 24/7, and make it accessible from any device via a public IP address.
What You'll Build
Open your phone browser, type in your server's public IP, tap the button, and get a random food suggestion served from a real server running on the other side of the internet.
By the end of this project, you'll have:
- A live deployment of your food randomizer app on an EC2 instance, accessible from any phone or computer via a public IP.
- A production-grade Nginx reverse proxy that serves your Vue static files and routes API requests to your Elysia backend.
- A PM2 process manager keeping your backend alive around the clock, auto-restarting on crashes even after you disconnect.
- Secret Mission: Configure PM2 startup so your app survives a full server reboot.
Are there any prerequisites?
This project assumes you have already completed the "วันนี้กินไร" web app (Elysia backend + Vue 3 frontend) running locally. Your code must be pushed to a GitHub repository before you start.
Before We Start
Before We Start...
Before we dive in, let's lock in what you're about to deploy and why it matters. Right now your "วันนี้กินไร" app only works on your laptop. By the end of this project, anyone with a phone can open a URL and get a random food suggestion.
Launch an EC2 Instance and SSH In
Your "วันนี้กินไร" app works perfectly on your laptop. That's great for testing, but nobody else can access it.
To make it available from any phone or computer, you need a server running on the internet. In this step, you'll launch a Linux server on AWS and connect to it remotely using SSH.
In this step, get ready to:
- Create an AWS account and launch an EC2 instance.
- SSH into the server from Windows PowerShell.
- Update system packages to prepare for installations.
Create an AWS account and launch an EC2 instance
An EC2 instance is a virtual server in Amazon's cloud. You'll launch one running Ubuntu Linux.
- Open your browser and navigate to https://console.aws.amazon.com.
✔️ I already have an AWS account
Sign in with your existing credentials and continue to the next step below.
ⓧ I need to create one
- Click Create an AWS Account.
- Enter your email address, choose a password, and provide payment details.
- Complete the identity verification and choose the Basic (Free) support plan.
- Once your account is ready, sign in to the AWS Management Console.
What about cost?
New AWS accounts created after July 15, 2025 receive $100 in credits and access to Free Tier instances (including t3.micro) for 6 months. Accounts created before that date get t3.micro free for 12 months at 750 hours/month.
Now launch your instance.
- In the top navigation bar, select a region close to you (e.g. Asia Pacific (Singapore) or US East (N. Virginia)).
- In the search bar at the top, type EC2 and click EC2 to open the EC2 dashboard.
- Click Launch instance.
- Under Name and tags, enter wankin-server as the instance name.
- Under Application and OS Images (Amazon Machine Image), click Quick Start and select Ubuntu. Choose an AMI marked Free tier eligible.
- Under Instance type, select t3.micro (Free tier eligible).
- Under Key pair (login), click Create new key pair.
- Enter wankin-key as the key pair name.
- Keep the key pair type as RSA and the file format as .pem.
- Click Create key pair. The wankin-key.pem file downloads automatically to your Downloads folder.
What is a key pair?
A key pair is like a password file for your server. AWS puts the public half on your instance, and you keep the private half (the .pem file) on your computer. SSH uses this file to prove your identity when connecting.
- Under Network settings, confirm the checkbox Allow SSH traffic from is selected with source set to Anywhere.
- Leave all other settings at their defaults.
- In the Summary panel on the right, click Launch instance.
You should see a Success banner confirming the launch.
- Click the instance ID link in the success notification to open the Instances page.
- Wait for the Instance state to change from Pending to Running.
- Select your instance and find the Public IPv4 address in the details panel below. Copy this IP address. You'll need it in a moment.
Save your public IP somewhere handy
Paste the IP into a notepad or sticky note. You'll use it many times throughout this project.
Connect to your instance via SSH
Your server is running in the cloud. Now you'll connect to it from your Windows machine using SSH.
- Press the Windows key to open your search bar.
- Type PowerShell and press Enter to open Windows PowerShell.
Before connecting, you need to fix the file permissions on your .pem key. Windows downloads files with open permissions, and SSH refuses to use a key that other users could read.
- Fix the key file permissions by running these commands:
icacls.exe $env:USERPROFILE\Downloads\wankin-key.pem /reset
icacls.exe $env:USERPROFILE\Downloads\wankin-key.pem /GRANT:R "$($env:USERNAME):(R)"
icacls.exe $env:USERPROFILE\Downloads\wankin-key.pem /inheritance:r
What do these commands do?
These three commands reset the file's permissions, grant read-only access to your Windows user, and remove inherited permissions from other users. This ensures only you can read the private key.
Before you run the SSH command below, what do you expect to see if the connection succeeds?
- Connect to your instance by running this command:
ssh -i $env:USERPROFILE\Downloads\wankin-key.pem ubuntu@[[EC2_IP="your EC2 public IPv4 address"]]
What is happening here?
The ssh command opens a secure connection to your remote server. The -i flag points to your private key file. ubuntu is the default username for Ubuntu EC2 instances.
- When prompted "Are you sure you want to continue connecting?", type yes and press Enter.
You should see the Ubuntu welcome message with system information. Your terminal prompt changes to something like ubuntu@ip-172-x-x-x:~$ confirming you are now inside the remote server.
Connection refused or timed out?
- Make sure your instance state shows Running (not Pending). It can take 1-2 minutes after launch.
- Confirm you copied the correct Public IPv4 address from the instance details (not the private IP).
- If you see "Permission denied (publickey)", double-check your .pem file path and that you ran the icacls permission commands above.
Help me troubleshoot my SSH connection to EC2.
Update system packages
Your server is running a fresh Ubuntu install. Before installing anything new, you need to update the package lists and upgrade existing packages to their latest versions.
- Update and upgrade system packages by running this command:
sudo apt update && sudo apt upgrade -y
What does this command do?
The sudo apt update part refreshes the list of available packages from Ubuntu's repositories. The sudo apt upgrade -y part installs the latest versions of all currently installed packages. The -y flag auto-confirms so you don't have to type yes for each upgrade.
You'll see a stream of output as packages download and install. This may take a minute or two. When it finishes, your prompt returns to ubuntu@ip-172-x-x-x:~$.
Upgrade seems stuck or asks questions?
- If a purple/pink screen appears asking about service restarts, press Enter to accept the default selection.
- If the process seems frozen, wait a few more minutes. Large security updates can take time on a micro instance.
Help me with my apt upgrade issue on EC2.
Your server is live and up to date. Next up, you'll install Bun, clone your project code, and prove your backend runs on the remote machine.
Install Bun, Clone Your Code, and Run the Backend
You have a fresh Ubuntu server running on AWS and you're connected via SSH. Right now it's an empty box with no runtime, no code, and no way to serve your app.
Your backend is built with Bun and Elysia, so you need Bun installed on this server before anything can run. Once the runtime is in place, you'll pull your code from GitHub and prove the backend responds.
In this step, get ready to:
- Install the Bun runtime on your EC2 server.
- Clone your project repository and install dependencies.
- Run your Elysia backend and confirm it responds with a random menu item.
Install Bun on the server
Bun's installer requires the unzip package, which minimal Ubuntu EC2 images may not include. You'll install that first, then run the Bun installer script.
- Install unzip (a prerequisite for the Bun installer) by running this command:
sudo apt install unzip -y
- Install Bun by running the official installer script:
curl -fsSL https://bun.com/install | bash
You'll see the installer download and extract Bun, then print a success message confirming where it was installed. It also adds Bun to your PATH in ~/.bashrc.
- Load the updated PATH into your current session and verify the installation by running:
source ~/.bashrc
bun --version
You should see a version number printed (e.g. 1.2.19 or newer). This confirms Bun is installed and ready to use.
What does this do?
- source ~/.bashrc reloads your shell configuration so the terminal session picks up the new Bun PATH without needing to disconnect and reconnect.
- bun --version prints the installed Bun version, proving the binary is reachable.
Not seeing a version number?
- If you see command not found, the PATH was not loaded. Run source ~/.bashrc again and retry.
- If the installer failed with a download error, check your instance has internet access. Run curl -I https://bun.com to test connectivity.
- help me troubleshoot Bun installation on my EC2 Ubuntu instance
Clone your repo and install dependencies
Your project code lives on GitHub. You'll pull it onto the server and install all its packages in one go.
- Clone your GitHub repository into the ~/app directory by running this command:
git clone [[REPO_URL="https://github.com/your-username/your-repo.git"]] ~/app
- Move into the project folder and install dependencies by running:
cd ~/app
bun install
You should see Bun download and install all packages listed in your package.json. It finishes in a few seconds.
What does this do?
- git clone downloads your entire repository (code, config, and history) from GitHub onto the server.
- ~/app places it in your home directory under a folder called app.
- bun install reads package.json and downloads every dependency your project needs into node_modules.
Clone failed?
- If you see repository not found, double-check that your repo is public on GitHub. Private repos require authentication.
- If bun install fails, make sure you ran cd ~/app first. Bun needs to find a package.json in the current directory.
- help me fix my git clone or bun install issue on EC2
Run the backend and confirm it responds
Time to prove your Elysia backend actually works on this server. You'll start it in one SSH session and test it from another.
- Start your backend by running this command (it will stay running in the foreground and take over this terminal):
bun run src/index.ts
Why does this take over my terminal?
The server process runs in the foreground, listening for requests continuously. It will not return you to a command prompt. You need a second terminal session to send test requests while the server is running.
- Open a new PowerShell window on your Windows machine.
- Connect to your server again using the same SSH command from Step 1:
ssh -i [[PEMPATH="path\to\your-key.pem"]] ubuntu@[[EC2_PUBLIC_IP="your EC2 public IP address"]]
Before you run the next command, what do you expect to get back from your API endpoint? Think about what your app returns when you hit the random menu route.
- In your second SSH session, test the backend by running:
curl http://localhost:3000/api/random
You should see a JSON response containing a random menu item from your app. This proves your Elysia backend is running successfully on the server.
What just happened?
curl sends an HTTP GET request to your backend running on port 3000. The /api/random endpoint returns a random food suggestion as JSON, just like it does on your local machine.
The backend is only reachable from inside the server right now (localhost). The outside world cannot access it yet. That comes in the next steps with Nginx and Security Groups.
Not seeing a JSON response?
- If bun run src/index.ts threw an error, check that you're inside ~/app and that bun install completed without errors.
- If curl shows Connection refused, make sure the backend is still running in your first SSH session. Look for error messages there.
- If your entry point file is named differently (not src/index.ts), adjust the command to match your project's actual entry file.
- help me debug why my Elysia backend is not responding on my EC2 server
- Once you've confirmed the JSON response, go back to your first SSH session and press Ctrl+C to stop the backend.
Your backend runs on the server. The problem is that if you close your SSH session right now, the process dies instantly. Next up, you'll hand control to PM2 so your app stays alive in the background forever.
Keep the Backend Alive with PM2
Your backend is running on the server and responding to requests. However, the moment you close your SSH session, the process dies with it.
PM2 is a process manager that runs your app as a background daemon. It keeps your backend alive independently of your terminal session and automatically restarts it if it crashes.
In this step, get ready to:
- Install PM2 globally and create a node symlink so PM2 works with Bun.
- Start your backend as a PM2-managed background process.
- Verify the app survives an SSH disconnect.
Install PM2 and create the node symlink
PM2's internal scripts use a node shebang to execute. Since your server only has Bun installed (no Node.js), you need to create a symbolic link that maps node to your Bun binary.
- Install PM2 globally and create the symlink by running these commands:
bun install pm2 -g && sudo ln -s $(which bun) /usr/local/bin/node
What do these commands do?
- bun install pm2 -g installs PM2 as a global package so you can run it from any directory.
- sudo ln -s $(which bun) /usr/local/bin/node creates a symbolic link that points /usr/local/bin/node to your Bun binary. This lets PM2's internal shebang (#!/usr/bin/env node) resolve correctly.
- Confirm PM2 is working by running:
pm2 --version
You should see a version number printed in your terminal.
pm2: command not found?
- Make sure Bun's global bin directory is in your PATH. Run source ~/.bashrc and try again.
- If the symlink failed because /usr/local/bin/node already exists, remove it first with sudo rm /usr/local/bin/node and re-run the symlink command.
- help me fix pm2 command not found on my Ubuntu EC2 instance with Bun installed
Start your backend with PM2
Instead of running Bun directly (which ties the process to your terminal), PM2 launches it as a detached daemon that survives disconnections.
- Switch to the SSH session where your backend is still running from Step 2.
- Press Ctrl+C to stop the manually running backend.
- Return to your main SSH session and start the backend with PM2 by running:
cd ~/app && pm2 start src/index.ts --name "wankin-api" --interpreter bun
What does this command do?
- pm2 start src/index.ts tells PM2 to launch your backend entry file as a managed process.
- --name "wankin-api" gives the process a recognizable name you can reference in other PM2 commands.
- --interpreter bun tells PM2 to use Bun (not Node) to execute the TypeScript file directly.
- Verify PM2 is managing your process by running:
pm2 list
You should see a table with wankin-api listed and its status showing online.
- View recent log output to confirm the backend started cleanly:
pm2 logs wankin-api --nostream
You should see log lines indicating your Elysia server is listening on port 3000.
Status shows errored instead of online?
- Check the error details with pm2 logs wankin-api --nostream.
- If port 3000 is already in use, you did not fully stop the manual process. Find it with lsof -i :3000 and kill it with kill <PID>, then restart PM2 with pm2 restart wankin-api.
- Confirm your entry point path is correct. Run ls ~/app/src/index.ts to verify the file exists.
- help me fix my PM2 process showing errored status
Confirm the app survives an SSH disconnect
The whole point of PM2 is that your app keeps running after you disconnect. Time to prove it.
Before you disconnect, what do you think will happen to the API when your SSH session closes? Will curl http://localhost:3000/api/random still work after you reconnect?
- Close your SSH session completely by running:
exit
- Wait a few seconds, then reconnect from Windows PowerShell using the same SSH command from Step 1:
ssh -i [[PEMPATH="path\to\your-key.pem"]] ubuntu@[[EC2="YOUR PUBLIC IP ADDRESS"]]
- Test that the API is still responding by running:
curl http://localhost:3000/api/random
You should see a JSON response with a random menu item. Your app survived the disconnect because PM2 is running it as a background daemon, completely independent of your terminal session.
No response after reconnecting?
- Run pm2 list to check if the process is still listed. If it shows stopped, restart it with pm2 restart wankin-api.
- If PM2 itself is not running, the symlink may have broken. Verify with ls -la /usr/local/bin/node and re-create if needed.
- help me debug why my PM2 process stopped after SSH disconnect
Your backend is now running 24/7, no matter how many times you connect and disconnect. Next up, you'll put Nginx in front of it to serve your Vue frontend and proxy API requests on port 80.
Set Up Nginx as Reverse Proxy
Your backend is alive and managed by PM2. Even if you close your SSH session, the API keeps running on port 3000. But nobody outside the server can reach port 3000 directly.
Nginx solves this by sitting in front of your app on port 80. It handles two jobs: serving your Vue frontend's static files to visitors, and forwarding any API requests to your Elysia backend running on port 3000.
In this step, get ready to:
- Build your Vue frontend into production-ready static files.
- Install Nginx and configure it as a reverse proxy.
- Enable the site config and verify everything works.
Build the Vue frontend
Your Vue app needs to be compiled into static HTML, CSS, and JavaScript files before Nginx can serve them. The build process produces a dist/ folder containing everything a browser needs.
- Build your frontend by running these commands:
cd ~/app/frontend && bun run build
- Confirm the build succeeded by listing the output folder:
ls ~/app/frontend/dist
You should see files like index.html and an assets/ folder containing your compiled CSS and JavaScript.
Build failed or no dist folder?
Make sure you are inside the ~/app/frontend directory. If there is no frontend folder, your repo structure may differ. Check your GitHub repo to confirm the correct path to your Vue project.
If you see dependency errors, run bun install inside the frontend folder first, then try the build again.
help me debug my Vue frontend build failing on my EC2 server
Install Nginx and create the site config
- Install Nginx by running:
sudo apt install nginx -y
- Confirm Nginx is running by hitting its default page:
curl http://localhost
You should see a wall of HTML containing "Welcome to nginx" in the output. This proves Nginx installed and started correctly.
Why does Nginx start automatically?
On Ubuntu, apt install nginx both installs and starts the service. It also enables Nginx to start on boot by default. You do not need to run a separate start command.
Now you need to replace the default page with a config that serves your app. Nginx uses individual site config files stored in /etc/nginx/sites-available/.
- Create the site config file by running this command:
sudo tee /etc/nginx/sites-available/wankin > /dev/null << 'EOF'
server {
listen 80;
server_name _;
# Serve Vue static files from the built dist folder
root /home/ubuntu/app/frontend/dist;
index index.html;
# Handle Vue Router (history mode) - fallback to index.html
location / {
try_files $uri $uri/ /index.html;
}
# Forward API requests to Elysia backend on port 3000
location /api/ {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
EOF
What does this config do?
- listen 80 tells Nginx to accept traffic on port 80, the standard HTTP port.
- root points to your Vue app's built static files. try_files $uri $uri/ /index.html handles client-side routing by falling back to index.html for any path Vue Router manages.
- location /api/ catches any request starting with /api/ and forwards it to your Elysia backend via proxy_pass.
Getting a "Permission denied" error?
Make sure you include sudo at the start of the command. Writing to /etc/nginx/ requires root privileges.
help me fix permission issues when creating Nginx config files on Ubuntu
Enable the site and reload Nginx
Your config file exists but Nginx is not using it yet. Nginx only loads configs that are symlinked into /etc/nginx/sites-enabled/. You also need to remove the default site so it does not conflict with yours.
- Enable your site, remove the default, test the config, and reload Nginx by running these commands:
sudo ln -s /etc/nginx/sites-available/wankin /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
What do these commands do?
- ln -s creates a symbolic link, which is how Nginx discovers which sites to serve.
- rm default removes the default "Welcome to nginx" page so only your app is served.
- nginx -t tests your config for syntax errors before applying it. You should see "syntax is ok" and "test is successful" in the output.
- systemctl reload nginx applies the new config without dropping any active connections.
Before you test, what do you think you will see when you curl http://localhost now? Will it be the Nginx welcome page, your Vue app, or an error?
- Test that Nginx is serving your Vue frontend:
curl http://localhost
You should see HTML output from your Vue app, including a <div id="app"> tag and references to your JavaScript bundle in the /assets/ path.
- Test that API requests are proxied to your Elysia backend:
curl http://localhost/api/random
You should see a JSON response with a random menu item. This is the same response you got from port 3000 earlier, but now it is served through Nginx on port 80.
Seeing a 502 Bad Gateway or empty response?
Confirm your backend is still running with pm2 list. If wankin-api shows "stopped" or "errored", restart it with pm2 restart wankin-api.
If you see a 404 for the static files, double-check that the root path in your config matches the actual location of your dist/ folder. Run ls /home/ubuntu/app/frontend/dist to confirm it exists.
If sudo nginx -t showed errors, check for typos in /etc/nginx/sites-available/wankin. You can view it with cat /etc/nginx/sites-available/wankin.
help me debug my Nginx reverse proxy returning errors instead of my app
Nginx is now routing all traffic exactly where it needs to go. Next up, you will open port 80 in AWS so the outside world can reach your app from any device.
Open the Port and Test from Your Phone
Your Nginx reverse proxy is working perfectly on the server. Both static files and API requests resolve when tested from inside the machine.
AWS blocks all incoming traffic by default. Even though your app responds on port 80 locally, nobody outside the server can reach it. The Security Group acts as a firewall, and right now it only allows SSH connections on port 22.
In this step, get ready to:
- Open port 80 (HTTP) in your EC2 Security Group.
- Test your app from a Windows browser using the public IP.
- Confirm access from your phone on mobile data.
Open port 80 in the Security Group
Your EC2 instance lives behind a Security Group that controls which traffic is allowed in. You need to add a rule permitting HTTP traffic on port 80 from anywhere on the internet.
- Switch to your browser where you have the AWS Management Console open.
- In the search bar at the top, type EC2 and select EC2 from the results.
- In the left sidebar, click Instances.
- Click on your instance ID to open its details.
- Select the Security tab in the lower panel.
- Click the Security Group link (it starts with sg-).
- Click Edit inbound rules.
You should see the existing SSH rule on port 22. Now you will add a second rule for HTTP traffic.
- Click Add rule.
- In the Type dropdown, select HTTP. This automatically fills in port 80.
- In the Source dropdown, select Anywhere-IPv4 (0.0.0.0/0).
- Click Save rules.
What does this rule do?
You just told AWS to allow any device on the internet to send HTTP requests to port 80 on your server. Nginx is already listening on port 80 inside the instance, so now external traffic can finally reach it.
The 0.0.0.0/0 source means "anywhere." For a web server meant to be publicly accessible, this is exactly what you want.
You should see your inbound rules list now showing two entries: SSH on port 22 and HTTP on port 80.
Don't see the HTTP rule after saving?
Make sure you clicked Save rules at the bottom of the page. If the rule disappeared, repeat the steps above. Confirm the Type is HTTP (not HTTPS or custom TCP).
Still stuck? Help me add an inbound rule to my EC2 Security Group for HTTP on port 80.
Test from your Windows browser
The Security Group is open. Time to find out if the outside world can reach your app.
- First, grab your instance's public IP. In the EC2 console, go back to Instances and find the Public IPv4 address column for your instance.
- Copy that IP address.
Before you test, what do you think will happen when you paste this IP into your browser? Will you see your Vue app, a connection error, or something else?
- Open a new tab in your Windows browser and navigate to:
http://[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]
You should see your Vue frontend load. The page displays your "วันนี้กินไร" food randomizer interface.
- Click the random button on the page.
A food suggestion appears. That means the frontend loaded via Nginx, the button triggered a request to /api/random, Nginx proxied it to your Elysia backend on port 3000, and the response came back. The full chain is working.
Page not loading or connection timed out?
- Make sure you are using http:// not https://. Your server does not have an SSL certificate, so HTTPS will not work.
- Confirm the Security Group rule saved correctly. Go back to your instance's Security tab and verify port 80 is listed with source 0.0.0.0/0.
- Check that Nginx is still running on the server by running sudo systemctl status nginx in your SSH session. It should say "active (running)."
Need more help? My app works with curl on the server but I can't access it from my browser using the public IP.
Confirm access from your phone on mobile data
Testing from your Windows browser proves the port is open. But your computer might be on the same network as your server's region. The true test is a completely separate network.
- On your phone, turn off WiFi and make sure you are on mobile data (4G/5G).
- Open your phone's browser.
- Type your EC2 public IP address into the address bar (the same IP you used on your computer).
- Tap the random button.
A food suggestion appears on your phone. Your app is live on the internet, accessible from any device anywhere in the world.
Why mobile data instead of WiFi?
WiFi on the same router as your computer might use the same public IP or routing path. Mobile data forces the request through your phone carrier's network, proving the app is reachable from a truly separate connection.
App not loading on your phone?
- Double-check that WiFi is off on your phone. Some phones auto-reconnect.
- Make sure you typed http:// before the IP. Some mobile browsers default to HTTPS.
- Verify the IP is correct. It should match the Public IPv4 address shown in your EC2 console.
Still not working? Help me figure out why my EC2-hosted app is not accessible from my phone on mobile data.
Your "วันนี้กินไร" app is live on the internet. Anyone with the IP can get a random food suggestion. But there's one vulnerability left. If the server ever restarts, PM2 and your app disappear. The Secret Mission fixes that.
Secret mission
Survive a Server Reboot
Your app runs great right now, but what happens when AWS performs maintenance and your server restarts? In production, apps must come back on their own. Configure PM2's startup integration so your app auto-resurrects after a full server reboot.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses an EC2 instance covered by the AWS Free Tier, but charges apply if you exceed your Free Tier limits.
Cost warning
If your Free Tier expires or you exceed limits, a running t3.micro costs approximately $0.0104/hour (~$7.50/month) in us-east-1. Always check the Billing dashboard in the AWS Console to monitor your Free Tier usage.
New accounts (created on or after July 15, 2025) get t3.micro free for 6 months. Legacy accounts (created before July 15, 2025) get t3.micro free for 12 months at 750 hours/month.
Resources you used:
- 1x EC2 instance (t3.micro) running continuously.
- 1x EBS volume (default 8 GB gp3) attached to the instance.
- 1x Key pair (.pem file stored on your Windows machine).
Keep everything running
No action needed. Choose this if you are still actively building or want the app to stay live.
- Your app stays accessible at your public IP on port 80.
- Monitor your Free Tier usage in the AWS Billing dashboard to avoid surprise charges.
- PM2 keeps the backend alive and Nginx continues serving traffic.
Pause - I'll come back to this later
Stop the instance to pause compute charges. Your EBS volume and data remain intact, but your public IP will change when you restart.
- Open the EC2 console at https://console.aws.amazon.com/ec2/.
- In the navigation pane, click Instances.
- Select your instance, then click Instance state and choose Stop instance.
- Confirm by clicking Stop.
What happens when you stop?
No compute charges while stopped. EBS storage still incurs a minimal cost (pennies per month for 8 GB). When you start the instance again, it gets a new public IP address. You will need to update your SSH command with the new IP.
Delete - I don't want to use this again
Terminate the instance to permanently delete the server, its data, and the attached EBS volume. This is irreversible.
Terminate the EC2 instance:
- Open the EC2 console at https://console.aws.amazon.com/ec2/.
- In the navigation pane, click Instances.
- Select your instance, then click Instance state and choose Terminate (delete) instance.
- Click Terminate (delete) when prompted for confirmation.
- The attached EBS volume is deleted automatically (Delete on Termination is enabled by default for the root volume).
Delete the key pair from AWS:
- In the EC2 console navigation pane, click Key Pairs.
- Select your key pair, then click Actions and choose Delete.
- Type Delete in the confirmation field and click Delete.
Delete the local .pem file from your Windows machine:
- Open PowerShell and delete your key file by running:
Remove-Item "C:\Users\YourName\Downloads\your-key.pem"
Replace the path above
Use the actual path where you saved your .pem file during Step 1.
That's a Wrap!
That's a Wrap!
Nice work! You just took your "วันนี้กินไร" app from running on localhost to running on a real server that anyone in the world can access.
You've learned how to:
- Launched an EC2 instance, connected via SSH from Windows, and installed Bun on a Linux server to run your Elysia backend.
- Configured PM2 to keep your app alive as a background daemon that auto-restarts on crashes.
- Set up Nginx as a reverse proxy serving Vue static files and forwarding API requests, then opened port 80 via a Security Group so the world can reach your app.
- Secret Mission: Configured PM2 startup with systemd so your app auto-starts after a full server reboot.
Ready to quiz yourself?