How to deploy a JavaScript application on DigitalOcean

How to deploy a JavaScript application on DigitalOcean

We may earn an affiliate commission through purchases made from our guides and tutorials.

“JavaScript app” can mean a few different beasts: a static front-end (Vite/React, Astro, vanilla), a Node.js server (Express/Fastify/Nest), or a full-stack framework (Next/Nuxt/SvelteKit) that outputs either static assets or a Node server. DigitalOcean supports all of that—either on App Platform (PaaS) or on a Droplet (your own Ubuntu box). I’ll show both paths so you can pick the right tool for the job and not regret a few months from now.

If you want minimal maintenance and automatic HTTPS, use App Platform. If you want full control, predictable performance, and classic Linux ergonomics, use a Droplet with Nginx + PM2. I’ll call out trade-offs along the way.

Choose your path (quick reality check)

  • App Platform (PaaS): Connect your GitHub/GitLab repo → pick Node.js or Static Site → deploy. It handles builds, rollbacks, autoscaling tiers, and automatic SSL/TLS on your custom domain. Great default for most projects and teams that prefer “push to deploy”.
  • Droplet (Ubuntu server): You manage Node, Nginx, firewall, SSL, processes (PM2/systemd). Slightly more work up front, but you get control, lower steady-state costs at scale, and fewer surprises. Common production pattern: Nginx reverse proxy → PM2-managed Node app → Let’s Encrypt via Certbot.

I’ll start with App Platform (create an account here) because it’s the fastest to production, then show the Droplet pattern that’s stood the test of time.

Path A — App Platform (push-to-deploy, automatic HTTPS)

1) Prepare your repo

Pin your Node version and define explicit build/run scripts so builds are deterministic. App Platform’s Node buildpack respects your engines.node, and it supports npm, Yarn, or pnpm—pick one and stick to it.

{
  "name": "my-app",
  "private": true,
  "engines": { "node": "22.x" },
  "scripts": {
    "build": "vite build",          // or next build / nuxt build / tsc etc.
    "start": "node server.js",      // for Node runtime; omit for static sites
    "preview": "vite preview"
  }
}

If your framework outputs a static site (e.g., Astro, Next export, SvelteKit static), plan to use App Platform’s Static Site component that just serves compiled assets—no runtime needed.

2) Create the app and deploy

  • In DigitalOcean, create an App and link your GitHub/GitLab repo. App Platform detects Node or Static Site automatically.
  • Set Environment Variables in the component’s Settings.
  • For Node apps: supply Build Command (e.g., npm ci && npm run build) and Run Command (e.g., npm run start).
  • Add your custom domain in Settings → Domains and let App Platform provision certs automatically (no Certbot needed).

That’s it—new commits auto-deploy. If you ever wondered “will it handle SSL?”, yes: automatic SSL/TLS with renewals.

3) Optional: CI/CD with GitHub Actions

App Platform can be triggered from Actions so you can run tests/lint/build checks before deploying.

name: deploy-to-digitalocean-app-platform

on:
  push:
    branches: [ "main" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        uses: digitalocean/app_action@v2
        env:
          DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DO_TOKEN }}
          APP_ID: ${{ secrets.DO_APP_ID }}
          SPEC_PATH: .do/app.yaml

DigitalOcean provides an official how-to and a marketplace Action for this flow.

When App Platform shines: you want speed, built-in HTTPS, rollbacks, and fewer knobs. When it bites: highly stateful or exotic workloads, or when you need very specific OS/network tuning.

Path B — Droplet (Ubuntu 24.04/22.04) with Nginx + PM2 (battle-tested classic)

This is the conservative, reliable setup ops folks have used for years. It scales from hobby to serious traffic if you size the Droplet and tune Nginx.

1) Provision and secure the Droplet

  • Create an Ubuntu 22.04/24.04 Droplet.
  • Add your SSH key, disable password auth, enable UFW (OpenSSH, Nginx Full).
  • Keep the box updated (sudo apt update && sudo apt upgrade -y).

We have a guide that covers Nginx installation.

2) Install Node.js (LTS) and your app

Use your preferred method (NodeSource, nvm). Then pull your code and install dependencies.

# example with NodeSource (Node 22.x)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs build-essential

# app directory
sudo mkdir -p /var/www/myapp && sudo chown $USER:$USER /var/www/myapp
cd /var/www/myapp
git clone https://github.com/you/myapp.git .
npm ci
npm run build

3) Run the Node process with PM2 (and make it survive reboots)

sudo npm i -g pm2
pm2 start "npm run start" --name myapp
pm2 save
pm2 startup systemd

4) Put Nginx in front (reverse proxy)

Create a server block that proxies to your Node app on localhost (say your app listens on 127.0.0.1:3000).

server {
    listen 80;
    server_name myapp.example.com;

    access_log /var/log/nginx/myapp.access.log;
    error_log  /var/log/nginx/myapp.error.log;

    location / {
        proxy_pass         http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Enable it and test:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

5) Add HTTPS with Let’s Encrypt (Certbot)

Point your domain’s DNS A record at the Droplet’s IP first. Then:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d myapp.example.com
sudo systemctl status certbot.timer

Certbot edits your Nginx config, installs a valid certificate, and sets up automatic renewal.

Now you’ve got the classic, sturdy stack: Nginx → PM2 → Node, with auto-renewing HTTPS.

Static vs. server-rendered frameworks on DO (what to pick)

  • Static (Vite/React static, Astro, Next “export”): On App Platform, use a Static Site component—give it a build command (e.g., npm run build) and a publish directory (dist, .next/out, build). It serves the compiled assets and handles SSL. Dead simple.
  • Server runtime (Express, Fastify, Next server mode, Nuxt server, SvelteKit w/ Node adapter): On App Platform, choose Node.js (or Docker). On a Droplet, use the Nginx + PM2 pattern above with Certbot for TLS.

Add a proper deployment pipeline (recommended)

Even on App Platform, gate deployments behind tests. On Droplets, a small GitHub Actions job can SSH and restart PM2 on success.

name: deploy-droplet

on:
  push:
    branches: [ "main" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: |
          npm ci
          npm run build
      - name: Deploy over SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.DROPLET_IP }}
          username: ${{ secrets.DROPLET_USER }}
          key: ${{ secrets.DROPLET_SSH_KEY }}
          script: |
            cd /var/www/myapp
            git pull --rebase
            npm ci
            npm run build
            pm2 reload myapp --update-env

For App Platform specific deployments from Actions, use DO’s official workflow/action.

Common gotchas (so you don’t learn the hard way)

  • Node version drift: Pin engines.node. App Platform’s default moves with LTS—avoid surprise build breaks.
  • Static routing 404s (Next/SPA): On static sites, ensure framework builds produce correct routes; use framework-specific adapter or redirects if needed. (Typical symptom: works locally, 404 on platform.)
  • HTTPS termination location: On App Platform, don’t roll your own HTTPS in Node—let the platform terminate TLS. On Droplets with Nginx, terminate in Nginx and keep Node on HTTP localhost.
  • Renewals: Certbot sets up timers; still good to verify certbot.timer status quarterly. DigitalOcean’s docs outline renewal behavior and checks.

Final checklists

App Platform checklist

[ ] package.json has engines.node and deterministic scripts
[ ] Repo connected; component type chosen (Static Site or Node.js)
[ ] Build/Run commands set; env vars added in Settings
[ ] Custom domain added; automatic SSL verified
[ ] Optional: GitHub Actions workflow gates deploys

Droplet checklist

[ ] Ubuntu updated; SSH hardened; UFW enabled
[ ] Node LTS installed; app builds locally on server
[ ] PM2 manages the process; pm2 save + startup
[ ] Nginx reverse proxy configured and tested
[ ] Certbot installed; HTTPS working; renew timer active

I’ve anchored this guide to stable primitives—Nginx + PM2 + Certbot on Droplets and buildpack + automatic TLS on App Platform. Those won’t age out next quarter. When DigitalOcean bumps defaults (say Node LTS), your engines.node pin keeps builds predictable, which is why I hammered on it earlier. If you ever swap frameworks (React → Svelte, Next → Astro), the deployment shapes (static vs Node runtime) still map directly to the same DO components.

Was this helpful?

Thanks for your feedback!
Alex is the resident editor and oversees all of the guides published. His past work and experience include Colorlib, Stack Diary, Hostvix, and working with a number of editorial publications. He has been wrangling code and publishing his findings about it since the early 2000s.

Leave a comment

Your email address will not be published. Required fields are marked *