/ astro-integrations / How to Integrate DigitalOcean with Astro: Complete Guide
astro-integrations 12 min read

How to Integrate DigitalOcean with Astro: Complete Guide

Step-by-step guide to integrating DigitalOcean with your Astro website. Setup, configuration, and best practices.

How to Integrate DigitalOcean with Astro: Complete Guide

How to Integrate DigitalOcean with Astro: Complete Guide

DigitalOcean provides cloud infrastructure for deploying web applications, with options ranging from managed app hosting to custom servers. For Astro projects, DigitalOcean offers two main deployment paths: App Platform for managed deployments with automatic builds, and Droplets for full control over your server environment. Both support static Astro sites and server-rendered applications.

This guide covers deploying Astro to DigitalOcean App Platform (the managed approach) and setting up a Droplet with Node.js for server-rendered (on-demand) Astro projects. It targets Astro 6 (the current major release), so the configuration reflects the current rendering model where static is the default and output: 'server' plus an adapter is what enables on-demand rendering.

Prerequisites

Before starting, you will need:

  • Node.js 22.12.0 or later installed locally. Astro 6 dropped support for Node 18 and Node 20, so Node 22 is now the floor (Node 24 also works). Verify with node --version.
  • An existing Astro project in a Git repository (GitHub, GitLab, or Bitbucket)
  • A DigitalOcean account (new accounts get free trial credit; the exact amount and duration vary by current promotion, so confirm on the signup page)
  • doctl CLI installed (optional but helpful): brew install doctl (Homebrew currently ships doctl 1.160.0)

Installation

For Static Sites

No additional packages needed. Astro builds static sites by default. In Astro 5 and 6 the output option only accepts 'static' (the default) or 'server'. The old output: 'hybrid' value was removed in Astro 5, so do not add it. If you previously used hybrid, the replacement is to leave output as the default 'static' and opt individual routes into on-demand rendering with export const prerender = false (an adapter is required for those routes).

For Server-Rendered Sites

For on-demand (SSR) rendering, install the Node.js adapter. The astro add command installs the latest adapter and wires it into your config automatically:

npx astro add node

As of this writing the adapter resolves to @astrojs/node 10.1.2, which declares a peer dependency of astro@^6.3.0. Pin it explicitly if you prefer:

npm install @astrojs/node@^10.1.2

Configure your astro.config.mjs:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'standalone' }),
});

The adapter has two modes. standalone produces a self-contained server that you start by running the entry module directly (used throughout this guide). middleware instead produces a handler you mount inside an existing Node server such as Express or Fastify. For App Platform and Droplet deployments, standalone is the right choice.

Configuration

DigitalOcean App Platform is a managed PaaS that builds and deploys your app from a Git repository. It handles SSL, CDN, and scaling automatically.

Step 1: Create App Spec

Create an .do/app.yaml file in your project root. For a static Astro site:

# .do/app.yaml
name: my-astro-site
static_sites:
  - name: web
    github:
      repo: your-username/your-repo
      branch: main
    build_command: npm run build
    output_dir: dist
    environment_slug: node-js
    envs:
      - key: NODE_ENV
        value: production

The output_dir field is optional. If you omit it, App Platform scans for a build output directory using these names in order: _static, dist, public, build. Astro builds static output to dist, so the auto-detection works without output_dir, but setting it explicitly is clearer.

For a server-rendered Astro site:

# .do/app.yaml
name: my-astro-app
services:
  - name: web
    github:
      repo: your-username/your-repo
      branch: main
    build_command: npm run build
    run_command: node dist/server/entry.mjs
    environment_slug: node-js
    http_port: 4321
    instance_size_slug: basic-xxs
    instance_count: 1
    envs:
      - key: HOST
        value: "0.0.0.0"
      - key: PORT
        value: "4321"
      - key: NODE_ENV
        value: production

App Platform requires the service to bind to 0.0.0.0 on the port declared in http_port. The Node adapter in standalone mode reads the HOST and PORT environment variables, so setting HOST=0.0.0.0 and PORT=4321 (matching http_port) is what makes the service reachable. Without HOST=0.0.0.0 the server binds to localhost only and App Platform marks the deployment unhealthy.

Step 2: Deploy via Dashboard

  1. Go to DigitalOcean Dashboard > Apps > Create App
  2. Connect your GitHub/GitLab repository
  3. Select the branch to deploy from
  4. DigitalOcean auto-detects the framework and suggests build settings
  5. Adjust the build command to npm run build and output directory to dist
  6. Choose your plan and region
  7. Click Deploy

Step 3: Deploy via CLI

Alternatively, deploy using the doctl CLI:

doctl auth init
doctl apps create --spec .do/app.yaml

Method 2: Droplet Deployment

For full server control, deploy Astro to a DigitalOcean Droplet.

Step 1: Create the Droplet

doctl compute droplet create astro-server \
  --image ubuntu-22-04-x64 \
  --size s-1vcpu-1gb \
  --region nyc1 \
  --ssh-keys YOUR_SSH_KEY_FINGERPRINT

Step 2: Server Setup

SSH into your Droplet and install Node.js:

ssh root@your-droplet-ip

# Install Node.js via nvm (v0.40.4 is the current nvm release)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
source ~/.bashrc
# Astro 6 needs Node 22.12.0 or later; install the Node 22 LTS line
nvm install 22
nvm use 22

# Install PM2 for process management
npm install -g pm2

# Install Nginx as reverse proxy
apt update && apt install -y nginx

Step 3: Deploy Your Astro App

# Clone your repository
git clone https://github.com/your-username/your-repo.git /var/www/astro-app
cd /var/www/astro-app

# Install dependencies and build
npm install
npm run build

# Start with PM2
pm2 start dist/server/entry.mjs --name astro-app
pm2 save
pm2 startup

Step 4: Configure Nginx

# /etc/nginx/sites-available/astro-app
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location / {
        proxy_pass http://localhost:4321;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

Enable the site and add SSL:

ln -s /etc/nginx/sites-available/astro-app /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

# Add SSL with Let's Encrypt
apt install -y certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com -d www.yourdomain.com

Common Patterns

Environment Variables on App Platform

Add environment variables through the DigitalOcean dashboard or the app spec:

envs:
  - key: DATABASE_URL
    value: "postgresql://user:pass@host:5432/db"
    type: SECRET
  - key: PUBLIC_SITE_URL
    value: "https://yourdomain.com"

Variables marked as SECRET are encrypted and hidden in the dashboard.

Connecting to Managed Database

DigitalOcean offers managed PostgreSQL, MySQL, and Redis. Connect your Astro app to a managed database:

# In app.yaml
databases:
  - name: db
    engine: PG
    version: "16"
    size: db-s-1vcpu-1gb
    num_nodes: 1

Reference the database connection string with ${db.DATABASE_URL} in your app's environment variables.

Auto-Deploy on Push

App Platform automatically deploys when you push to the configured branch. For more control, disable auto-deploy and use the CLI or API:

# Trigger manual deployment
doctl apps create-deployment YOUR_APP_ID

Custom Domain Setup

In the DigitalOcean dashboard, go to your app's Settings > Domains. Add your custom domain and point your DNS to DigitalOcean's nameservers or add the provided CNAME record.

# If using DigitalOcean DNS
doctl compute domain create yourdomain.com
doctl compute domain records create yourdomain.com \
  --record-type CNAME \
  --record-name www \
  --record-data your-app.ondigitalocean.app.

Troubleshooting

Build fails on App Platform: Check the build logs in the DigitalOcean dashboard. Common issues include missing environment variables during build time, Node.js version mismatches, or memory limits on the smallest instance size. Upgrade to a larger instance if builds run out of memory.

"Cannot find module" error on a server-rendered deployment: Make sure your package.json lists all runtime dependencies under dependencies, not devDependencies. Production installs skip dev dependencies (modern npm uses npm install --omit=dev; older docs called this --production), so anything imported by the running server must be a regular dependency.

Port binding error: App Platform expects your app to listen on the port specified in the http_port config and to bind to 0.0.0.0 rather than localhost. The Node adapter in standalone mode reads the HOST and PORT environment variables, so set HOST=0.0.0.0 and a PORT that matches http_port. If HOST is left unset the server binds to localhost and the health check fails.

Static site returning 404 on routes: For static sites with client-side routing, add a catch-all route configuration. In App Platform, set catchall_document: index.html under your static_sites entry in the app spec, which serves index.html for any path that does not match a built file. Note that only one of catchall_document or error_document can be set on a static site. The cleaner option for a content site is to configure your Astro project to generate all routes at build time so every URL has a real HTML file.

static_sites:
  - name: web
    source_dir: /
    catchall_document: index.html

SSL certificate not working on Droplet: Certbot may fail if your DNS has not propagated yet. Verify your domain points to the Droplet's IP address with dig yourdomain.com. Also make sure ports 80 and 443 are open in your firewall: ufw allow 80 && ufw allow 443.

High memory usage on Droplet: Node.js can consume significant memory for on-demand rendering. Set a memory limit with PM2: pm2 start dist/server/entry.mjs --max-memory-restart 512M. For static sites, serve the dist directory directly with Nginx instead of running Node.js.

Common Errors and Fixes

These are the doc-grounded gotchas that catch people most often when pairing Astro 6 with DigitalOcean.

output: 'hybrid' is not a valid config value. Astro removed the hybrid output mode in Astro 5, and Astro 6 keeps only 'static' (default) and 'server'. A config that still sets output: 'hybrid' will error on build. The fix is to leave output as the default 'static' and mark only the routes that need a server with export const prerender = false, or set output: 'server' and mark the static ones with export const prerender = true. An adapter is required for any on-demand route.

Adapter missing for on-demand routes. If you add export const prerender = false (or set output: 'server') without an adapter installed, the build fails because Astro has no server runtime target. Run npx astro add node so the build emits dist/server/entry.mjs.

Node version too old for Astro 6. Astro 6 requires Node 22.12.0 or later and dropped Node 18 and 20. On App Platform, the node-js environment slug provides a recent Node, but if you pin a Node version through an .nvmrc, engines field, or node-js buildpack setting, make sure it is at least 22.12.0. On a Droplet, install Node 22 with nvm as shown above. A too-old Node typically surfaces as a syntax or "unsupported engine" error during npm run build.

Service bound to localhost instead of 0.0.0.0. App Platform routes traffic to the port in http_port and the container must listen on 0.0.0.0. The Node standalone server reads HOST and PORT from the environment, so set HOST=0.0.0.0 and PORT equal to http_port. Omitting HOST leaves the server on localhost and the deployment fails its health check.

Runtime dependency placed in devDependencies. Production installs omit dev dependencies (modern npm uses npm install --omit=dev). Anything the running server imports must live in dependencies, including @astrojs/node. A "Cannot find module" at runtime usually means a dependency landed in the wrong section of package.json.

Static single-page routes 404 because there is no catch-all. App Platform serves real files for static sites. If your site relies on client-side routing for paths that have no built HTML file, set catchall_document: index.html under the static_sites entry. Only one of catchall_document or error_document may be set. For a content-first Astro site, prefer generating every route at build time so each URL maps to a real file.

Build output directory not detected. When output_dir is omitted, App Platform scans for _static, dist, public, then build. Astro outputs to dist, so detection works, but if you customize Astro's outDir you must set output_dir to match or the deploy will publish an empty or wrong directory.

Official Docs and Examples

Conclusion

DigitalOcean gives you flexibility in how you deploy Astro. App Platform provides the simplest path with managed builds, auto-deploy, and SSL included. Droplets offer full control when you need custom server configurations, additional services, or tighter cost management. For most Astro projects, start with App Platform for simplicity. Move to a Droplet when you need more control over the server environment or want to run multiple services alongside your Astro application. Static sites can use the lowest App Platform tier, making it a cost-effective choice for blogs and marketing sites.

Sources

Versions and configuration facts in this guide were verified against the following sources, checked on 2026-05-29: