Two sites, one repo: monorepo deploys to Dokku
Running tishandavid.com (Angular) and tishandavid.dev (Next.js) from a single repository — two Dokku apps, path-filtered CI, and no accidental cross-deploys.
The problem
I had one repository with two completely different sites in it: an Angular SSR app in website/ and a Next.js app in tishandaviddev/. Both needed to deploy to the same Dokku box, on different domains, without one site's commits triggering a rebuild of the other.
The naive setup — one Dockerfile at the repo root — only builds one app. I needed two.
The architecture
The trick is that Dokku lets each app build from a different subdirectory of the same pushed repo. So:
tdavid(the .com app) builds the rootDockerfile, which targetswebsite/.tddev(the .dev app) is configured withbuild-dir tishandaviddev, so it buildstishandaviddev/Dockerfile.
Then CI pushes the repo to whichever app's files actually changed.
The code that mattered
Each app gets a self-contained Dockerfile. For the Next.js app I lean on output: "standalone" so the runtime image is tiny:
FROM node:22-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-slim AS run
WORKDIR /app
ENV NODE_ENV=production PORT=3000
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]Telling Dokku where to build from is a single command:
dokku builder:set tddev build-dir tishandaviddevAnd the CI is two path-filtered workflows, so a change under tishandaviddev/** only ever deploys tddev:
on:
push:
branches: [main]
paths:
- "tishandaviddev/**"
- ".github/workflows/deploy-dev.yml"The .com workflow has the mirror-image filter on website/**. They can't step on each other.
Results
- Two independent apps from one
git push, each on its own domain with its own Let's Encrypt cert. - A commit that only touches
.devnever rebuilds.com— CI time roughly halved versus deploying both every push. - Total hosting is one ~A$9/month VPS running both apps plus a few others.
What I'd do differently
output: "standalone" in a monorepo needs outputFileTracingRoot pinned to the app directory, or Next traces files from the repo root and bloats the bundle. I learned that from a 400MB image before pinning it — set it from day one.
I'd also add a tiny smoke-test step after each deploy (curl the homepage, assert a 200 and a known string) so a green build that boots into a broken page can't pass silently.