Next.js app at your own domain
Create a Next.js app in a workspace, build it for production, and put it on
the internet at app.example.com, with HTTPS that renews itself. At the end
it also comes back on its own after the server restarts.
The same steps work for a Nest.js API; see Nest.js instead at the end.
You need: a Linux VPS (tested on Debian and Ubuntu), and a domain such as app.example.com
pointed at it (or a DNS provider package installed, so expose add points it
for you — see DNS).
Before you start: machine, skills, workspace
curl -fsSL https://mydevmachine.sh/install.sh | sh
devmachine setup
devmachine skills add
devmachine workspaces new acme
devmachine sync
Already have a machine? Skip setup. Already have the workspace? Skip the
last two. See getting started for what each command
does.
By hand
Node is already there: every new workspace gets the dev package, which
installs the Node LTS (with npm and npx) through mise. If you made the
workspace with your own --packages list and left dev out, add it first
on your computer: devmachine packages add dev --workspace acme, then
devmachine sync.
Caddy came with setup (it’s in the essentials package), and it will
answer this site and get its own certificate. If your machine was set up
with --no-essentials, add it first: devmachine packages add caddy, then
devmachine sync.
1. Create the app
Inside the workspace:
devmachine ssh acme
cd ~/dev
npx create-next-app@latest web --yes --use-npm
The first time, npx asks to install create-next-app: answer y.
--yes takes the defaults for every other question (TypeScript, Tailwind,
ESLint, the App Router), and --use-npm installs the packages with npm.
2. Build it and start it
Still inside the workspace:
cd ~/dev/web
npm run build
npm start
npm run build makes the optimized production build, and npm start serves
it on port 3000. Do not serve the site with npm run dev: the dev server
is slower, rebuilds pages on each request, and shows its error overlay to
visitors.
3. Expose the port
On your computer:
devmachine expose add acme 3000 --host app.example.com --publish
expose add writes the Caddy route on the machine and reloads Caddy, which
gets the certificate for the host — no sync needed. If no DNS provider
package is installed, expose add prints the record to create by hand at
your registrar instead of writing it for you.
With your agent
Open a session on your own computer (devmachine skills add taught it the
CLI) and say:
In my devmachine workspace acme, create a Next.js app in ~/dev/web with
create-next-app, build it with npm run build, and run it with npm start on
port 3000 under pm2, so it comes back after a reboot. Then expose port 3000
at app.example.com.
The agent runs the same commands over SSH: create-next-app, the build,
pm2 and the cron line from Keep it running, then
expose add on your computer. You still point app.example.com at the
server yourself if no DNS provider package is installed — the agent shows
you the record. And if setup has not run yet, you check the fingerprint
yourself; the agent cannot do that part.
Check it: on your computer,
curl -s https://app.example.com | grep -o '<title>[^<]*</title>' prints
<title>Create Next App</title>, with a valid certificate.
Keep it running
devmachine ssh acme opens inside tmux, so npm start keeps going after
you close the terminal — see keep your sessions running.
A reboot stops it. To bring it back on its own, run it under
pm2 instead.
First stop the npm start from step 2 with Ctrl-C. Then, inside the
workspace:
npm install -g pm2
cd ~/dev/web
pm2 start "npm start" --name web
pm2 save
(crontab -l 2>/dev/null; echo "@reboot $HOME/.local/share/mise/shims/pm2 resurrect") | crontab -
pm2 save writes down the apps pm2 runs now, and the cron line starts them
again at every boot. pm2’s own pm2 startup does not work here: it only
prints a sudo command, and a workspace account has no sudo. The cron
line does the same job without it. It uses the mise shim, the full path to
pm2, because cron runs with almost nothing on its PATH.
Once the code lives in a git repository you push to, ship a new version with:
cd ~/dev/web
git pull && npm run build && pm2 restart web
pm2 logs web shows the app’s output, and pm2 ls shows whether it is
online.
Nest.js instead
A Nest.js API follows the same flow. Inside the workspace:
cd ~/dev
npx @nestjs/cli@latest new api --package-manager npm --no-observe
cd api
npm run build
node dist/main
--package-manager npm and --no-observe answer the two questions
nest new would ask. The API listens on port 3000 by default, and
curl localhost:3000 prints Hello World!. Expose it the same way, on your
computer:
devmachine expose add acme 3000 --host app.example.com --publish
To keep it running, stop node dist/main with Ctrl-C and run it under pm2
instead, then save and add the same cron line as above:
cd ~/dev/api
pm2 start dist/main.js --name api
pm2 save
Port 3000 holds one app at a time. To run the Next.js site and the API side
by side, start the API on another port — the generated src/main.ts reads
it from PORT: PORT=3001 pm2 start dist/main.js --name api — and expose
that port at another host, such as api.example.com.
Troubleshooting
pm2 start npm --name web -- start fails with SyntaxError: Unexpected identifier 'pipefail'. The npm that mise installs is a shell script,
and that form makes pm2 run it as JavaScript. pm2 ls shows the app as
errored. Delete it with pm2 delete web and start it as a command
string, as above: pm2 start "npm start" --name web.
Remove it
On your computer, take the site off Caddy:
devmachine expose rm app.example.com
Then, inside the workspace, stop the app and drop the cron line:
pm2 delete web
pm2 save --force
crontab -l | grep -v 'pm2 resurrect' | crontab -
Source: Next.js — Deploying, create-next-app, NestJS — First steps, PM2 — Quick start
Where to go next
- Web apps
Express site with TLS
Run an Express app in a workspace and serve it at your own domain, with HTTPS.
Beginner
- Web apps
FastAPI app at your own domain
Run a FastAPI app in a workspace with uv and serve it at your own domain, with HTTPS and its API docs.
Beginner
- Workspaces
Keep your sessions running
Close the laptop, and Claude Code keeps working on the server inside tmux.
Beginner