A Dockerfile for a DigitalOcean side project

2023-09-01 · Ben Williamson

Why I finally wrote this down

I moved a personal n8n instance from a prebuilt container image to a custom Dockerfile on DigitalOcean App Platform. The goal was simple: bake community nodes into the image so redeploys did not wipe them. The friction was not Docker syntax. It was App Platform's spec defaults assuming /Dockerfile at repo root while my file lived in a subdirectory.

Once dockerfile_path and source_dir matched reality, a 512 MB basic-xs instance was enough. This post captures the boring contract that made that work.

What belongs in the image

Build code, not identity:

  • Install dependencies in a stable layer order (package.json or requirements first, then copy source).

  • Bake community nodes or OS packages the app needs at build time if redeploys reset the filesystem.

  • Expose the port App Platform expects (EXPOSE documents intent; the spec's http_port is authoritative).

  • Leave tokens, domains, and database URLs to runtime environment variables.

If I cannot build the same image on a fresh laptop and deploy it without editing the Dockerfile for a hostname, the image is too specific.

Minimal Node starting point:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

For n8n, base FROM the official image and add a layer that installs community nodes, or copy a prepared nodes manifest. Confirm whether your platform rebuilds from scratch on each deploy (DigitalOcean does). That determines what must be baked vs mounted.

DigitalOcean App Platform shape

Keep the platform contract explicit in the app spec:

  • GitHub source with dockerfile_path pointing at the real file (not default root).

  • source_dir set when the Dockerfile context is a subdirectory.

  • http_port matching what the container listens on (5678 for n8n).

  • health_check.http_path proving the process booted (often / with generous initial_delay_seconds).

  • envs documented beside the Dockerfile in README, not only in the cloud UI.

Managed Postgres (or another DB) gets its own block in the spec. Inject host, user, and password via env vars scoped to RUN_AND_BUILD_TIME only when truly needed at build; prefer runtime-only for secrets.

What not to bake in

  • API tokens and master keys (rotate via platform env, not rebuild).

  • Local SQLite files or dev databases copied into the image by accident.

  • Environment-specific hostnames in application code when env vars exist.

  • Private registry credentials in layers; use build-time secrets support if the platform provides it.

Side-project ops rule

Prefer one container until the product proves it needs more. Fancy entrypoint scripts become weekend debugging. DigitalOcean wants a port, a start command, env vars, and a health check. Give it that, document the list, and go write features.

Quick review before first deploy

  • Does dockerfile_path match where the file actually lives?

  • Are community nodes installed at build time if the platform wipes runtime installs?

  • Is every secret in env vars with a rotation note?

  • Does health check wait long enough for n8n or Node cold start?