Docker Basics Start Your First App Fast DevOps and Cloud Tutorial by Offensotech
A situation most coders have lived through at least once.
A week goes by while you craft a thing that actually feels right. Smooth on your machine. Then someone else tries it - maybe a colleague, maybe a remote system - and suddenly there's a note: "Doesn’t work." You check what went wrong. The problem sounds alien. Unfamiliar. So you wonder which Node version sits in their setup. Not the one you used. That question about the library comes up again. Still missing. Two hours vanish while matching setups piece by piece - only to wonder later if it was ever the script, the settings, or just everything refusing to line up right.
This issue - called “works on my machine” - has haunted developers forever. One of the most stubborn headaches in coding history. Yet Docker wipes it out entirely.
Understanding Docker Without the Jargon?
Picture yourself relocating to another city. Rather than stuffing items into crates, wondering if they’ll make it intact or even match the new space, you lift the whole room - fixtures, outlets, lights, drapes, all fixed just so - and shift it entirely. Once settled, it appears and functions precisely how it always has, no matter the structure housing it.
This is how Docker handles your app. Not only the code comes along, yet also every piece it relies on - think runtime, libraries, settings - bundled together. This bundle, known as a container, behaves exactly alike wherever Docker exists. Whether it sits on your computer. Or lands on someone else's machine. Even if shipped to a distant server across continents. Identical outcome, without exception.
Three Key Words
Starting out with Docker might seem confusing because of the unique terms used. Yet deep down, just three ideas matter most when beginning.
Picture an image as a set of clear directions. Not quite a suggestion - more like exact steps for how things must appear: the operating system, the code language, every file, all required tools. Once built, it stays fixed, never changing. Think of it as something you can reuse again without altering the original. From just one, multiple running spaces can come into being whenever needed.
A single portion comes straight from following those cooking steps. This version lives and works using the blueprint given. Starting it means Docker builds a separate space shaped by that template, letting your software operate within. The moment it spins up, everything needed rides along inside.
A set of directions lives on a basic text document called a Dockerfile. This sheet holds clear steps, one after another, showing Docker exactly how to assemble your image. Each line acts like a note telling what comes next.
Done. Picture, box, recipe file - those three pieces form everything else inside Docker. All the rest just builds from here.
Install Docker
Head over to docker.com, pick the right version of Docker Desktop for your computer's OS - options include Mac, Windows, or Linux. Once downloaded, complete setup, launch the app, then access your terminal to execute:
bash
docker --version
A number showing up means it's good to proceed.
Create a Basic App for Containerizing
A small server fits well into our container. Try using Node.js here instead; it keeps things clear so attention stays on Docker, not tangled code.
Inside your system, make a space named docker-demo. Within that spot, place two separate files.
First, package.json:
Json
{
"name": "docker-demo",
"version": "1.0.0",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.0"
}
}
Then, server.js:
Javascript
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('<h1>Hello from inside a Docker container!</h1>');
});
app.listen(3000, () => {
console.log('App is running on port 3000');
});
Hello from inside a Docker container!
Hello from inside a Docker container!
Just simple. One path gives back a message. Exactly what we need here.
Write Your Dockerfile
Right here matters more than anywhere else. Inside your project’s main folder, drop a file named Dockerfile - nothing after the name, just those letters - sitting at the top level
Dockerfile
Start with an official Node.js base image
FROM node:20-alpine
Set the working directory inside the container
WORKDIR /app
Copy package files first (this helps Docker cache dependencies)
COPY package.json ./
Install dependencies inside the container
RUN npm install
Copy the rest of your application code
COPY . .
Tell Docker which port the app uses
EXPOSE 3000
The command to start the app when the container runs
CMD ["node", "server.js"]
Picture walking beside someone, talking things through step by step. Imagine how you’d describe it while sipping coffee together. Think of the way stories flow when there’s no rush. Suppose clarity comes not from effort but just showing up. See each part unfold as if already known. Watch understanding grow without forcing it. Feel the ease of words matching thought.
Picture picking your base at the store - we go with node:20-alpine. It's like grabbing a ready-made kitchen setup where Node.js runs on Alpine Linux. That version of Linux takes up almost no space. Because of that, what we build stays tiny.
Inside the container, a directory named app comes into existence - this spot becomes the default workspace moving forward. Starting here means every following step takes place within that folder. Think of it as stepping through a door where all activity centers around what's inside. From this point onward, location matters because everything operates relative to this base. The moment it sets up, context shifts entirely to this space
Drop the package.json somewhere first, after that trigger npm to install. Imagine setting down a shopping list before bringing groceries inside. Inside Docker, everything piles up in levels. Leave that file untouched, next builds skip right through here. Every repeat gives moments back, quietly.
Here comes the remaining part of the code. We’re pulling in what was left behind earlier. Next up, the pieces that were waiting off to the side. The leftover sections step into view now. What remained outside enters the frame. Code segments held back before start joining in. Those bits set aside are moving forward here.
Marking EXPOSE 3000 means the blueprint notes communication via port 3000. Not until launch does any actual opening occur. Running it brings the connection live.
Starting up a container using this image triggers node server.js automatically. This instruction kicks in right at launch, every single time. Every run follows this path without exception.
Step 4: Create a .dockerignore File
Just like .gitignore tells Git which files to ignore, .dockerignore tells Docker which files to leave out of the image. Create one in your project root:
node_modules
.env
.git
*.log
This is important for two reasons. First, your local node_modules folder should not go into the image. Docker will install a clean copy inside the container. Second, your .env file, which contains secrets, should never end up inside an image that might be shared or uploaded.
Step 5: Build the Image
Now we actually create the image. From inside your project folder, run:
bashdocker build -t docker-demo:latest .
The -t flag gives the image a name: docker-demo. The word after the colon, latest, is the version label. The . at the end tells Docker the Dockerfile is in the current folder.
You will see Docker go through each step in your Dockerfile. The first time, it will download the base image, which takes a minute. Every subsequent time, it will use cached layers and run much faster.
When it finishes, check that your image was created:
bashdocker images
Step 6: Run the Container
bashdocker run -p 3000:3000 docker-demo:latest
The -p 3000:3000 part means "connect port 3000 on my laptop to port 3000 inside the container." Open your browser at http://localhost:3000 and you will see your application running from inside a Docker container.
To run it in the background, so it does not hold your terminal hostage:
bashdocker run -d -p 3000:3000 --name my-demo-app docker-demo:latest
To see what is currently running:
bashdocker ps
To stop it:
bashdocker stop my-demo-app
Step 7: Pass in Environment Variables
Real apps have configuration items such as database URLs, API keys, and feature flags. You pass these into a container at runtime so they never get included in the image:
bashdocker run -d -p 3000:3000 \
-e NODE_ENV=production \
-e PORT=3000 \
--name my-demo-app \
docker-demo:latest
For longer lists of variables, use --env-file .env to load them from a file on your machine without typing each one individually.
What Comes After This?
Once you are comfortable with single containers, the next step is Docker Compose. This tool lets you run multiple containers together with one command. A typical web project has a backend, a database, and maybe a caching layer. Docker Compose allows you to define all three in a single file and start them all at once.
For anyone building full-stack JavaScript apps, especially those in a MERN stack course in Kochi, Docker Compose is where things get really exciting. Instead of installing MongoDB locally and managing it yourself, you define it as a service in a Compose file. It starts automatically alongside your application, configured the same way every time.
Beyond that, the same concepts you learned today—images, containers, ports, environment variables—are the foundation for Kubernetes, the platform that supports how some of the largest companies in the world deploy software at scale.
The Lesson Docker Teaches You
There is a deeper idea behind all this. Docker forces you to be clear about your environment. You must write down exactly what your application needs to run. This discipline—the habit of thinking clearly about your dependencies, your configuration, and your runtime—makes you a better developer, even on projects that do not use Docker at all.
The "works on my machine" problem disappears not only because the environment travels with the code. It disappears because you finally took the time to describe the environment properly.
Do this once, and you will never want to go back.
Published by Offensotech—empowering developers to build, ship, and scale with confidence.
Comments
Post a Comment