Hands-On: Deploy a Node App on Lightsail
What You Will Build
A Lightsail instance running the Node.js blueprint, with a static IP that survives restarts, your own Express app deployed and served on port 80.
Step 1 — Create the Instance
- Open the Lightsail console (it has its own console, separate from the main AWS one).
- Click Create instance.
- Region: pick the one closest to your users.
- Platform: Linux/Unix. Blueprint: Node.js (under Apps + OS — it's a Bitnami image with Node, npm, and Apache preinstalled).
- Instance plan: the $5 (1 GB RAM) plan is comfortable for a small API. The $3.50 plan works but swaps under npm installs.
- Name:
node-app-1, then Create instance.
It's running in under a minute.
Step 2 — Attach a Static IP
The default public IP changes every time the instance stops. Fix that first, before you configure DNS or share the address anywhere:
- Go to Networking → Create static IP.
- Select the same region, attach it to
node-app-1, name itnode-app-1-ip.
Static IPs are free while attached to a running instance. An unattached one bills hourly, so release it if you delete the instance.
Step 3 — Connect via SSH
The browser terminal (the orange icon on the instance card) works for quick checks, but for real work download the default key from Account → SSH keys, then:
chmod 400 ~/Downloads/LightsailDefaultKey-us-east-1.pem
ssh -i ~/Downloads/LightsailDefaultKey-us-east-1.pem bitnami@YOUR_STATIC_IP
Note the user is bitnami, not ubuntu — this is a Bitnami blueprint.
Step 4 — Look Around the Blueprint
The Node.js blueprint ships with a Bitnami stack under /opt/bitnami:
node -v # preinstalled Node LTS
sudo /opt/bitnami/ctlscript.sh status # apache already running on 80/443
Apache is serving the default Bitnami landing page. The plan: run our Express app on port 3000 and have Apache proxy port 80 to it.
Step 5 — Deploy the Express App
mkdir ~/app && cd ~/app
npm init -y
npm install express
Create ~/app/server.js:
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.json({ app: 'lightsail-node', status: 'running' });
});
app.listen(PORT, () => console.log(`Listening on ${PORT}`));
For a real project, clone from Git instead of writing files on the server:
cd ~ && git clone https://github.com/your-user/your-app.git app
cd app && npm ci
Step 6 — Keep the App Running
The blueprint doesn't include a process manager, so install PM2:
sudo npm install -g pm2
cd ~/app
pm2 start server.js --name node-app
pm2 startup systemd # run the sudo command it prints
pm2 save
Step 7 — Proxy Port 80 Through Apache
Create a vhost config so Apache forwards traffic to the Express app. Create the file /opt/bitnami/apache/conf/vhosts/node-app-vhost.conf:
<VirtualHost 127.0.0.1:80 _default_:80>
ServerAlias *
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
</VirtualHost>
Then restart Apache:
sudo /opt/bitnami/ctlscript.sh restart apache
Visit http://YOUR_STATIC_IP — you should see the JSON from Express instead of the Bitnami page.
Step 8 — Open Ports in the Lightsail Firewall
Lightsail has its own firewall per instance, separate from anything on the OS. Manage it under instance → Networking tab → IPv4 Firewall.
Defaults are SSH (22) and HTTP (80) open. Adjust to:
| Application | Port | Source | Note |
|---|---|---|---|
| SSH | 22 | Restrict to your IP | Click the rule, enable source IP restriction |
| HTTP | 80 | Any | Public traffic |
| HTTPS | 443 | Any | Add this now for the TLS step later |
Don't open 3000 — Apache proxies to it locally, so it never needs public exposure. That's the point of the proxy setup.
Step 9 — Snapshot Before You Experiment
Now that the instance works, take a snapshot so you can roll back:
- Instance → Snapshots tab → Create snapshot, name it
node-app-1-working.
Snapshots bill at $0.05/GB/month — cheap insurance. I take one before every risky change on Lightsail boxes since there's no other undo.
Common Problems
| Symptom | Likely Cause |
|---|---|
| Port 80 still shows the Bitnami page | Apache didn't pick up the vhost — check the file path and restart output |
| Site unreachable after instance restart | You're using the old ephemeral IP — use the static IP |
EACCES on npm install -g | Prefix with sudo on Bitnami images |
| App down after reboot | pm2 save was skipped after pm2 startup |