DigitalOcean
beginner
30-40 minutes

App Platform Deployment

Deploy web applications to DigitalOcean App Platform - the fully managed PaaS with automatic Git deployments.

What you'll accomplish

Your application running on App Platform with automatic deployments

Prerequisites

  • DigitalOcean account
  • doctl CLI installed
  • Code in GitHub, GitLab, or container registry

What is App Platform?

App Platform is DigitalOcean's fully managed PaaS:

  • Git-based deployments - Push to deploy
  • Automatic scaling - Handle traffic spikes
  • Free SSL - Managed certificates
  • Built-in CI/CD - Build and deploy automatically
  • Component-based - Services, workers, jobs, databases
Git Push → Build → Deploy → Scale
   │          │        │       │
   └── GitHub/GitLab   │       └── Automatic
              └── Buildpacks/Docker
                        └── App Platform

App Platform vs Droplets

FeatureApp PlatformDroplets
ManagementFully managedSelf-managed
DeploymentGit pushManual/SSH
ScalingAutomaticManual
SSLAutomaticManual
Price$5+/mo$4+/mo
ControlLimitedFull
Note

Choose App Platform for most web apps. Use Droplets when you need full server control.

Step 1: Prepare Your Application

Supported Runtimes

RuntimeDetection
Node.jspackage.json
Pythonrequirements.txt or Pipfile
Gogo.mod
RubyGemfile
PHPcomposer.json
Staticindex.html
DockerDockerfile

Node.js Requirements

json
// package.json
{
  "name": "myapp",
  "scripts": {
    "start": "node server.js",
    "build": "npm run build"
  },
  "engines": {
    "node": "20.x"
  }
}
javascript
// server.js - Use PORT environment variable
const port = process.env.PORT || 8080;
app.listen(port, '0.0.0.0', () => {
  console.log(`Server running on port ${port}`);
});

Python Requirements

txt
# requirements.txt
flask==3.0.0
gunicorn==21.2.0
python
# app.py
import os
from flask import Flask
app = Flask(__name__)

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 8080))
    app.run(host='0.0.0.0', port=port)

Dockerfile (Custom)

dockerfile
FROM node:20-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

ENV PORT=8080
EXPOSE 8080

CMD ["node", "server.js"]

Step 2: Create App Spec

Create .do/app.yaml in your repository:

Basic Web Service

yaml
name: myapp
region: nyc

services:
  - name: web
    github:
      repo: username/myapp
      branch: main
      deploy_on_push: true
    build_command: npm run build
    run_command: npm start
    http_port: 8080
    instance_size_slug: basic-xxs
    instance_count: 1
    routes:
      - path: /

With Environment Variables

yaml
name: myapp
region: nyc

services:
  - name: web
    github:
      repo: username/myapp
      branch: main
      deploy_on_push: true
    http_port: 8080
    instance_size_slug: basic-xs
    envs:
      - key: NODE_ENV
        value: production
      - key: DATABASE_URL
        scope: RUN_TIME
        type: SECRET
        value: ${db.DATABASE_URL}

With Database

yaml
name: myapp
region: nyc

services:
  - name: web
    github:
      repo: username/myapp
      branch: main
    http_port: 8080
    instance_size_slug: basic-xs
    envs:
      - key: DATABASE_URL
        scope: RUN_TIME
        value: ${db.DATABASE_URL}

databases:
  - name: db
    engine: PG
    production: false

Static Site

yaml
name: my-static-site
region: nyc

static_sites:
  - name: website
    github:
      repo: username/my-site
      branch: main
    build_command: npm run build
    output_dir: dist
    routes:
      - path: /

Worker (Background Jobs)

yaml
name: myapp
region: nyc

services:
  - name: web
    github:
      repo: username/myapp
      branch: main
    http_port: 8080

workers:
  - name: worker
    github:
      repo: username/myapp
      branch: main
    run_command: npm run worker
    instance_size_slug: basic-xxs
    instance_count: 1

Scheduled Job

yaml
name: myapp
region: nyc

jobs:
  - name: cleanup
    github:
      repo: username/myapp
      branch: main
    run_command: npm run cleanup
    kind: PRE_DEPLOY
    instance_size_slug: basic-xxs

Step 3: Deploy the App

Via CLI

bash
# Create app from spec
doctl apps create --spec .do/app.yaml

# Get app ID
doctl apps list

# Update existing app
doctl apps update APP_ID --spec .do/app.yaml

Via Control Panel

  1. Go to cloud.digitalocean.com/apps
  2. Click Create App
  3. Select source (GitHub, GitLab, Docker Hub)
  4. Configure build settings
  5. Add resources (database, etc.)
  6. Configure environment variables
  7. Click Create Resources

Step 4: Instance Sizes

Available Sizes

SizeCPURAMPrice
basic-xxsShared512 MB$5/mo
basic-xsShared1 GB$10/mo
basic-sShared2 GB$20/mo
basic-mShared4 GB$40/mo
professional-xs1 vCPU1 GB$12/mo
professional-s1 vCPU2 GB$25/mo
professional-m2 vCPU4 GB$50/mo

Choosing Size

  • Basic: Shared CPU, good for low-traffic apps
  • Professional: Dedicated CPU, for production workloads

Step 5: Environment Variables

In App Spec

yaml
envs:
  # Plain text (visible)
  - key: NODE_ENV
    value: production

  # Secret (encrypted)
  - key: API_KEY
    type: SECRET
    value: your-api-key

  # Build-time only
  - key: NPM_TOKEN
    scope: BUILD_TIME
    type: SECRET
    value: token

  # Run-time only
  - key: DATABASE_URL
    scope: RUN_TIME
    value: ${db.DATABASE_URL}

  # Reference another component
  - key: REDIS_URL
    scope: RUN_TIME
    value: ${redis.REDIS_URL}

Via CLI

bash
# List app environment
doctl apps get APP_ID --format Spec

# Update via spec
doctl apps update APP_ID --spec .do/app.yaml

Via Control Panel

  1. App → Settings → App-Level Environment Variables
  2. Or component-specific: Components → Service → Environment Variables

Step 6: Custom Domains

Add Domain

bash
# Via Control Panel:
# App → Settings → Domains → Add Domain

DNS Configuration

Point your domain to App Platform:

TypeNameValue
CNAMEwwwyour-app.ondigitalocean.app
ALIAS/ANAME@your-app.ondigitalocean.app

Or use DigitalOcean DNS:

  1. Add domain to your account
  2. Update nameservers at registrar
  3. App Platform manages records automatically

SSL Certificates

SSL is automatic and free:

  • Let's Encrypt certificates
  • Auto-renewal
  • Force HTTPS option

Step 7: Scaling

Horizontal Scaling

yaml
services:
  - name: web
    instance_count: 3
    instance_size_slug: professional-xs

Auto-Scaling

yaml
services:
  - name: web
    instance_size_slug: professional-xs
    autoscaling:
      min_instance_count: 1
      max_instance_count: 10
      metrics:
        cpu:
          percent: 70

Manual Scaling

bash
# Update instance count via spec
doctl apps update APP_ID --spec updated-spec.yaml

Step 8: Logs and Monitoring

View Logs

bash
# Stream live logs
doctl apps logs APP_ID --follow

# Component-specific logs
doctl apps logs APP_ID web --follow

# Build logs
doctl apps logs APP_ID --type BUILD

# Deploy logs
doctl apps logs APP_ID --type DEPLOY

Via Control Panel

  1. App → Runtime Logs (live)
  2. App → Activity (deploy history)

Metrics

View in Control Panel:

  • CPU usage
  • Memory usage
  • Request count
  • Response time
  • Bandwidth

Step 9: Deployments

Trigger Deployment

bash
# Create new deployment
doctl apps create-deployment APP_ID

# Force rebuild
doctl apps create-deployment APP_ID --force-rebuild

Automatic Deployments

When deploy_on_push: true, pushing to the configured branch triggers deployment.

Rollback

  1. App → Activity
  2. Find previous deployment
  3. Click Redeploy

Deployment Workflow

Push to main → Build starts → Tests run → Deploy → Health check
      │             │            │          │           │
      └─ Trigger    └─ Minutes   └─ CI      └─ Replace  └─ Verify

Step 10: Database Integration

Dev Database (Free)

yaml
databases:
  - name: db
    engine: PG
    production: false  # Free dev database

Limited to:

  • 1 GB storage
  • Shared resources
  • Good for development

Production Database

yaml
databases:
  - name: db
    engine: PG
    production: true
    cluster_name: my-cluster
    db_name: myapp
    db_user: myuser

Or use existing Managed Database:

yaml
services:
  - name: web
    envs:
      - key: DATABASE_URL
        scope: RUN_TIME
        type: SECRET
        value: "postgresql://user:pass@host:port/db?sslmode=require"

Complete Examples

Next.js Application

yaml
name: nextjs-app
region: nyc

services:
  - name: web
    github:
      repo: username/nextjs-app
      branch: main
      deploy_on_push: true
    build_command: npm run build
    run_command: npm start
    http_port: 3000
    instance_size_slug: basic-xs
    envs:
      - key: NODE_ENV
        value: production

Full-Stack App

yaml
name: fullstack-app
region: nyc

services:
  - name: api
    github:
      repo: username/myapp
      branch: main
    source_dir: /api
    build_command: npm run build
    run_command: npm start
    http_port: 8080
    instance_size_slug: professional-xs
    envs:
      - key: DATABASE_URL
        scope: RUN_TIME
        value: ${db.DATABASE_URL}
    routes:
      - path: /api

static_sites:
  - name: frontend
    github:
      repo: username/myapp
      branch: main
    source_dir: /frontend
    build_command: npm run build
    output_dir: dist
    envs:
      - key: VITE_API_URL
        value: ${api.PUBLIC_URL}/api
    routes:
      - path: /

databases:
  - name: db
    engine: PG
    production: true

Troubleshooting

Build Fails

bash
# Check build logs
doctl apps logs APP_ID --type BUILD

Common issues:

  • Missing dependencies in package.json
  • Wrong Node.js version
  • Build command error

App Won't Start

  • Check run_command is correct
  • Verify app listens on PORT environment variable
  • Check runtime logs for errors

Database Connection Failed

  • Verify DATABASE_URL is set correctly
  • Check database is in same region
  • Verify SSL mode if required

Deployment Stuck

bash
# Cancel deployment
doctl apps create-deployment APP_ID --force-rebuild

# Or via Control Panel: Activity → Cancel
Note

Checkpoint: Deploy your application to App Platform, verify it's running at the provided URL, and test automatic deployments by pushing a change.