Deploy Docker Container To Digitalocean

Deploy Docker Container To Digitalocean

Reading time1 min
#Cloud#Docker#DevOps#DigitalOcean#Containerization

Deploy Docker Containers to DigitalOcean: Practical, Fast, No-Nonsense

There’s a persistent misconception that container orchestration must mean Kubernetes. In reality, spinning up containers on DigitalOcean can be done quickly, without the overhead or the cognitive load of managing a Kubernetes cluster. For many production workloads, droplets plus Docker are more than enough—especially when you want predictable costs, direct control, and minimal moving parts.

Snapshot: Why Use Docker on DigitalOcean?

Docker offers application portability; DigitalOcean provides simplicity, consistency, and just enough abstraction. Combined, these enable direct deployment pipelines with fewer failure domains and shorter feedback loops. No managed control planes. Direct access to logs. The network is yours to debug.

Note: App Platform covers PaaS use cases (builds from source or container), but this guide targets the hands-on route: provisioning droplets, running containers directly, integrating with CI/CD.


Prerequisites

  • Source code already containerized (e.g. for Node.js, Python, etc.)
  • Valid DigitalOcean account with billing enabled
  • doctl v1.103.0+ (DigitalOcean CLI) installed and authenticated (doctl auth init)
  • Docker 24.x or newer installed locally for building and pushing images

Reference Example: Minimal Node.js Service

The following Dockerfile illustrates a best-practice build for a Node.js app—nothing fancy, but production-lean:

FROM node:18.20-alpine3.19

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

EXPOSE 3000
CMD ["node", "app.js"]

Build and verify locally:

docker build -t my-node-app:202406 .
docker run --rm -p 3000:3000 my-node-app:202406
# Check http://localhost:3000

If you see an error such as Module not found: Error: Cannot find module 'express', revisit your package.json or context.


Registry: Docker Hub vs DigitalOcean Container Registry

Images must be retrievable from your droplet. DOCR is preferred for private builds and faster region access.

A. Docker Hub

docker tag my-node-app:202406 yourdockerhub/my-node-app:202406
docker push yourdockerhub/my-node-app:202406

B. DigitalOcean Container Registry (DOCR)

  1. Create registry:
    doctl registry create myregistry
    
  2. Authenticate:
    doctl registry login
    
  3. Tag and push:
    docker tag my-node-app:202406 registry.digitalocean.com/myregistry/my-node-app:202406
    docker push registry.digitalocean.com/myregistry/my-node-app:202406
    

Gotcha: DOCR rate-limits requests from unauthenticated agents. Use doctl registry login before any automated deployments.


Droplet Provisioning: Quick/Direct Route

Provision a base Ubuntu 22.04 LTS droplet via Control Panel or CLI:

doctl compute droplet create web01 \
  --region nyc3 --image ubuntu-22-04-x64 --size s-1vcpu-1gb \
  --ssh-keys <ssh-key-fingerprint> --tag-names docker --wait
  • Default 1 vCPU/1GB RAM sufficient for test deployments; for higher concurrency, select s-2vcpu-2gb or above.
  • SSH:
    ssh root@<DROPLET_IP>
    

Install Docker on Droplet

On a new droplet, avoid snap packages (they break on headless/CI bootstraps). Use official repositories:

apt-get update
apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/docker.gpg
echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
| tee /etc/apt/sources.list.d/docker.list

apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io

systemctl enable --now docker
docker --version

Deploy and Run Container

Login to Registry (on droplet):

  • For DOCR:
    doctl registry login
    docker login registry.digitalocean.com
    

Pull and Launch:

docker pull registry.digitalocean.com/myregistry/my-node-app:202406

docker run -d -p 80:3000 --restart unless-stopped --name node-web-app \
  registry.digitalocean.com/myregistry/my-node-app:202406

# For Docker Hub:
# docker pull yourdockerhub/my-node-app:202406
# docker run -d -p 80:3000 --restart unless-stopped --name node-web-app \
#   yourdockerhub/my-node-app:202406

Verify:

  • Hit http://<droplet_ip> from browser or use curl -i http://<droplet_ip> for headers.
  • If the port conflicts due to fail2ban or Nginx, either choose a different port or stop the conflicting service.

(Optional) Systemd for Container Lifecycle Management

To guarantee automatic restarts (and proper logging), wrap your container in a systemd unit. Example file:
/etc/systemd/system/nodeweb.service

[Unit]
Description=Node.js App Container
After=network-online.target docker.service
Requires=docker.service

[Service]
Restart=always
RestartSec=3
ExecStart=/usr/bin/docker start -a node-web-app
ExecStop=/usr/bin/docker stop -t 10 node-web-app

[Install]
WantedBy=multi-user.target

Then:

systemctl daemon-reload
systemctl enable --now nodeweb

Known issue: Systemd restarts will not recreate the container if you alter command flags or update the image tag. Use docker rm -f ... && docker run ... to change config.


(Alternative) DigitalOcean App Platform: PaaS, No SSH

Use cases: CI/CD pipeline from GitHub, multi-environment builds, integrated load balancing, HTTPS by default.

Steps:

  1. Push code or image to GitHub/DOCR.
  2. App Platform UI: "Create App", select repo or custom image.
  3. Specify exposed port.
  4. Configure horizontal/vertical scaling, add domains, set runtime variables.
  5. Deploy.

Trade-off: Debugging networking or layer 7 details is more opaque; direct SSH access is unavailable.


Production Hardening and Caveats

  • Lock down ports via DigitalOcean cloud firewall, not just UFW.
  • Tag droplets with environment (prod, staging, etc) for better doctl filtering.
  • For persistent data, mount DO Volumes (/mnt/volume_nyc1_*) and use Docker volumes: -v /mnt/volume_nyc1_xyz:/app/data
  • Watch for orphaned containers: docker ps -a can reveal if containers exited unexpectedly (e.g., OOMKilled).

Wrapping Up

Direct Docker deployment on DigitalOcean offers a balance between transparency and manageability. Not every workload needs full orchestration; for stateless services or MVP staging, droplets suffice. For those requiring rapid rollback or canary deploys, invest time in templated systemd units and registry-based automation.

Further reading: study DO load balancer integration, renovate image tags with semantic versioning, or trial DO's managed Postgres for stateful backing.

Any blocking errors, logs, or improvement ideas? Open a comment below; real failure cases are the best source of learning.