© Ujjawal Solanki | All Rights Reserved
There was a phase where every Node deployment felt like a private ritual. Install dependencies. Match the Node version. Recreate some environment variable from memory. Restart the service and hope the machine agreed with my laptop.
That routine looked manageable until it failed at the worst moments. A package behaved differently on the server. A teammate had a slightly different environment. Production became a place where small mismatches turned into late-night stress.
Docker and CI/CD started to matter to me when I got tired of treating consistency like luck.
The phrase “it works on my machine” sounds funny until you are the one defending it. Usually it means the app depends on setup details nobody wrote down clearly enough.
My local Node version drifted. Server packages drifted. Manual deploy steps drifted. The code was not the only thing changing between environments.
A deploy process that depends on memory will eventually fail at the exact moment you most need it to behave.
Docker gave me a repeatable box. Same operating assumptions. Same runtime. Same install flow. That did not remove every deployment bug, but it removed a whole class of avoidable ones.
CI/CD added the second half of the promise: not only a consistent environment, but a consistent path from push to release.
My first Dockerfiles were more confusing than the app. I copied multi-stage patterns before understanding why they existed.
For many backend apps, a simple base image, clear working directory, dependency install, and startup command are enough to begin.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 5000
CMD ["node", "server.js"]I like being able to start the app and its supporting services the same way every time. It reduces setup friction for me and for anyone else joining the project.
services:
api:
build: .
ports:
- '5000:5000'
env_file:
- .env
depends_on:
- mongo
mongo:
image: mongo:7
ports:
- '27017:27017'The first automation win is not a full production release. It is simply learning as early as possible that the app stopped installing, testing, or building correctly.
name: ci
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test --if-presentOnce tests and installs are reliable, the next step is letting the pipeline build the same artifact you expect to run in staging or production.
That closes the gap between “the code merged” and “the deploy artifact is trustworthy.”
CI/CD is helpful when it removes repeated manual work. It becomes dangerous when it automates a process you still do not understand.
I want the pipeline to restart services, pull known images, and run clear commands. I do not want it hiding mystery steps from the team.
This often came down to missing environment variables, incorrect working directories, or files excluded by `.dockerignore`.
Container logs became my best friend here. The issue was usually simple once I stopped guessing.
That usually meant the pipeline proved build health but not runtime assumptions. CI and deployment confidence are related, but they are not identical.
Read container logs early. A crashed process inside Docker is still just a process telling you what it dislikes.
Deployments became less emotional. Teammates onboarded faster. I trusted releases more because the path stopped depending on perfect memory.
That is the real gift of Docker and CI/CD for most Node apps. Not complexity. Predictability.
If this helped you, save it for later. And if you are stuck on a MERN feature, you can always reach out to me.
© Ujjawal Solanki | All Rights Reserved