
Deploy the application your repository actually builds. Cursor and other coding tools can help create code, but they do not determine its production runtime, database or authentication requirements. A Node.js app needs a running process; uploading its source to a static web directory is insufficient.
Inventory the application
Check its Node version, package manager, lockfile, production start script and listening port. List any databases, queues, file storage, email delivery, AI APIs and scheduled work. Confirm whether those services remain hosted elsewhere or run separately on your infrastructure.
The downloadable node-api example deliberately has no database and no package dependencies. It demonstrates a health route, a protected read-only route and graceful shutdown. It is a reference for deployment mechanics rather than a complete customer login system.
Download the reference projects (ZIP) for static, Node.js and Next.js examples. Read the included README and choose the example matching your application.
Run as a non-root application user
The image listens on port 3000 inside its container. It does not need root to bind that port.
FROM node:24-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY --chown=node:node package.json server.mjs ./
USER node
EXPOSE 3000
HEALTHCHECK --interval=15s --timeout=3s --start-period=10s CMD node -e "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "server.mjs"]
Test a local health check first
docker build -t my-node-app:release-1 .
docker run --rm -p 127.0.0.1:18081:3000 my-node-app:release-1
Visit http://127.0.0.1:18081/healthz; expect a small success response. The sample’s /api/message returns unauthorized until a token file is explicitly configured. A healthy process does not by itself prove that a database, payment provider or downstream API is functioning.
Provide secrets without baking them into the image
For the Compose sample, create secrets/demo-token.txt containing a long random test token and keep that file outside Git. Restrict access on the server. Compose mounts the file as /run/secrets/demo_token; the sample reads it using DEMO_TOKEN_FILE. The private token is never part of its public frontend.
After starting the service, request the protected endpoint with a valid bearer token using a client that does not expose it in logs or shared shell history. A missing or wrong token must receive an unauthorized response. Replace this demonstration mechanism with the authentication and authorization design appropriate to your real app.
Prepare the server and domain
Use a supported Linux image available in your VPS account and confirm compatibility with the runtime you install. Create an administrator account, configure SSH authentication, apply security updates and retain a recovery route. Install Docker Engine and its Compose plugin using the instructions for that distribution.
For the examples, reserve ports 80 and 443 for Caddy. Keep the application port private to the Docker network. Docker-published ports have their own firewall behaviour: confirm the effective rules and do not assume a host firewall automatically blocks every published container port.
Connect the intended hostname to the server using the supplied IP address. Review existing A and AAAA records, preserve email records and verify DNS before requesting HTTPS certificates. Test sign-in callbacks and third-party webhook URLs against the final HTTPS hostname.
Keep runtime ports private
The production Compose file uses Docker networking between the proxy and app. It does not publish port 3000 on the host. Add only the public entry points the application needs. If a worker has no inbound HTTP function, it may need no published port at all.
Confirm restart and shutdown behaviour
Use an explicit production start command and a restart policy. Handle shutdown signals so that active work can finish or be retried safely. For queues and scheduled tasks, design idempotent jobs and account for duplicate execution during restarts.
Common failures
- Works only in a terminal: an interactive development process is not the deployed service.
- Proxy cannot connect: check container health, network names and listening interface.
- Unexpected unauthorized response: confirm the secret mount, permissions and token value without printing the secret.
- Data disappears on replacement: the app wrote state into a disposable container instead of planned persistent storage.
Release, verify and recover
Deploy a specific reviewed commit and record its image version. Check application routes, login, permissions, an error path and the health check before sending visitors to it. Keep logs free of tokens and personal information. Test a restart so that a temporary interactive shell is not the only thing keeping the application running.
Keep recoverable copies of persistent data, required configuration and the previous application image outside the server. A container restart policy is not a backup. Rehearse a restore into a separate environment. Reverting an application image does not automatically reverse a database migration; design backward-compatible changes and keep a migration recovery plan.