Full Stack Developer specializing in React, Node.js, and scalable web solutions.

I am Ujjawal Solanki, a dedicated software developer with expertise in full-stack development, React applications, and Node.js backend services. I create innovative, scalable, and user-friendly web applications.

Docker and CI/CD for Node.js Apps

I Was Tired of “It Works on My Machine”

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.

What kept going wrong before Docker

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.

Why Docker felt like relief, not hype

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.

Step 1

Start with a small, readable Dockerfile

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"]
Step 2

Use docker-compose or a similar tool for local confidence

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'
Step 3

Add a CI pipeline that proves the app can still build

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-present
Step 4

Build and publish an image in the pipeline when the basics are stable

Once 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.”

Step 5

Automate the boring deployment actions, not the risky assumptions

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.

Troubleshooting the first containerized deployments

The app worked locally but not inside the container

This often came down to missing environment variables, incorrect working directories, or files excluded by `.dockerignore`.

The image built, but the service crashed on boot

Container logs became my best friend here. The issue was usually simple once I stopped guessing.

CI passed, but deploy still failed

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.

What changed after I embraced repeatability

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.