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
| Feature | App Platform | Droplets |
|---|---|---|
| Management | Fully managed | Self-managed |
| Deployment | Git push | Manual/SSH |
| Scaling | Automatic | Manual |
| SSL | Automatic | Manual |
| Price | $5+/mo | $4+/mo |
| Control | Limited | Full |
Note
Choose App Platform for most web apps. Use Droplets when you need full server control.
Step 1: Prepare Your Application
Supported Runtimes
| Runtime | Detection |
|---|---|
| Node.js | package.json |
| Python | requirements.txt or Pipfile |
| Go | go.mod |
| Ruby | Gemfile |
| PHP | composer.json |
| Static | index.html |
| Docker | Dockerfile |
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)
dockerfileFROM 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
yamlname: 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
yamlname: 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
yamlname: 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
yamlname: 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)
yamlname: 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
yamlname: 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
- Go to cloud.digitalocean.com/apps
- Click Create App
- Select source (GitHub, GitLab, Docker Hub)
- Configure build settings
- Add resources (database, etc.)
- Configure environment variables
- Click Create Resources
Step 4: Instance Sizes
Available Sizes
| Size | CPU | RAM | Price |
|---|---|---|---|
basic-xxs | Shared | 512 MB | $5/mo |
basic-xs | Shared | 1 GB | $10/mo |
basic-s | Shared | 2 GB | $20/mo |
basic-m | Shared | 4 GB | $40/mo |
professional-xs | 1 vCPU | 1 GB | $12/mo |
professional-s | 1 vCPU | 2 GB | $25/mo |
professional-m | 2 vCPU | 4 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
yamlenvs: # 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
- App → Settings → App-Level Environment Variables
- 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:
| Type | Name | Value |
|---|---|---|
| CNAME | www | your-app.ondigitalocean.app |
| ALIAS/ANAME | @ | your-app.ondigitalocean.app |
Or use DigitalOcean DNS:
- Add domain to your account
- Update nameservers at registrar
- 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
yamlservices: - name: web instance_count: 3 instance_size_slug: professional-xs
Auto-Scaling
yamlservices: - 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
- App → Runtime Logs (live)
- 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
- App → Activity
- Find previous deployment
- 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)
yamldatabases: - name: db engine: PG production: false # Free dev database
Limited to:
- 1 GB storage
- Shared resources
- Good for development
Production Database
yamldatabases: - name: db engine: PG production: true cluster_name: my-cluster db_name: myapp db_user: myuser
Or use existing Managed Database:
yamlservices: - 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
yamlname: 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
yamlname: 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_commandis correct - Verify app listens on
PORTenvironment variable - Check runtime logs for errors
Database Connection Failed
- Verify
DATABASE_URLis 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.