<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joe Seun</title>
    <description>The latest articles on DEV Community by Joe Seun (@joe_seun).</description>
    <link>https://dev.to/joe_seun</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3776106%2F5bc99439-2ce7-4a44-aa76-f1ef4549d051.jpg</url>
      <title>DEV Community: Joe Seun</title>
      <link>https://dev.to/joe_seun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joe_seun"/>
    <language>en</language>
    <item>
      <title>Building a Containerized Parent Registration Portal with Docker A Hands-On Guide to Dockerizing a Full-Stack Application</title>
      <dc:creator>Joe Seun</dc:creator>
      <pubDate>Mon, 16 Feb 2026 16:19:27 +0000</pubDate>
      <link>https://dev.to/joe_seun/building-a-containerized-parent-registration-portal-with-docker-a-hands-on-guide-to-dockerizing-a-2io8</link>
      <guid>https://dev.to/joe_seun/building-a-containerized-parent-registration-portal-with-docker-a-hands-on-guide-to-dockerizing-a-2io8</guid>
      <description>&lt;p&gt;📋 Table of Contents&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What the Project Does

Architecture Overview

Dockerfile Explanation

Docker Compose Breakdown

Volumes and Networking

Challenges Faced

Lessons Learned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🎯 What the Project Does&lt;/p&gt;

&lt;p&gt;The Parent Registration Portal is a simple yet practical web application that allows parents to register themselves and their children through an online form. But beyond its functionality, this project serves as a perfect demonstration of Docker containerization in action.&lt;br&gt;
Core Functionality:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Interface: A clean, blue-themed form where parents enter their name, address, phone number, and child's name

Data Processing: When submitted, the form sends data to a backend API

Data Storage: Information is permanently stored in a PostgreSQL database

Confirmation: Users receive immediate feedback on successful registration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Why This Project?&lt;/p&gt;

&lt;p&gt;This application was built to demonstrate how modern applications can be containerized using Docker, making them:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Portable: Run anywhere Docker is installed

Scalable: Easy to add more instances

Consistent: Same behavior in development and production

Isolated: Services run independently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🏗️ Architecture Overview&lt;/p&gt;

&lt;p&gt;The application follows a three-tier architecture pattern, with each tier running in its own Docker container:&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐&lt;br&gt;
│                 │     │                 │     │                 │&lt;br&gt;
│    Frontend     │────▶│     Backend     │────▶│    Database     │&lt;br&gt;
│     (Nginx)     │     │  (Node.js/API)  │     │  (PostgreSQL)   │&lt;br&gt;
│                 │     │                 │     │                 │&lt;br&gt;
└─────────────────┘     └─────────────────┘     └─────────────────┘&lt;br&gt;
         │                       │                       │&lt;br&gt;
         └───────────────┬───────┴───────────┬───────────┘&lt;br&gt;
                         │                   │&lt;br&gt;
                    ┌────┴────┐         ┌────┴────┐&lt;br&gt;
                    │ Custom  │         │Persistent│&lt;br&gt;
                    │ Network │         │ Volume   │&lt;br&gt;
                    │mentees- │         │postgres_ │&lt;br&gt;
                    │  net    │         │  data    │&lt;br&gt;
                    └─────────┘         └─────────┘&lt;/p&gt;

&lt;p&gt;Service Breakdown:&lt;br&gt;
Service Technology  Purpose Port&lt;br&gt;
Frontend    Nginx + HTML/CSS/JS Serves the user interface   8080&lt;br&gt;
Backend Node.js + Express   Handles API requests and business logic 3100&lt;br&gt;
Database    PostgreSQL  Stores registration data persistently   5432&lt;br&gt;
Communication Flow:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User accesses http://localhost:8080 in their browser

Frontend serves HTML/CSS/JS files

User fills form and clicks submit

JavaScript sends POST request to backend API (http://localhost:3100/register)

Backend validates data and inserts into PostgreSQL

Response returns to frontend with success/error message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🐳 Dockerfile Explanation&lt;br&gt;
Backend Dockerfile&lt;br&gt;
dockerfile&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Base Image Selection
&lt;/h1&gt;

&lt;p&gt;FROM node:16-alpine&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Working Directory
&lt;/h1&gt;

&lt;p&gt;WORKDIR /app&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Dependency Installation
&lt;/h1&gt;

&lt;p&gt;COPY package.json .&lt;br&gt;
RUN npm install&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Copy Application Code
&lt;/h1&gt;

&lt;p&gt;COPY . .&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Expose Port
&lt;/h1&gt;

&lt;p&gt;EXPOSE 3100&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Start Command
&lt;/h1&gt;

&lt;p&gt;CMD ["node", "server.js"]&lt;/p&gt;

&lt;p&gt;Line-by-Line Explanation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Base Image Selection: FROM node:16-alpine&lt;/p&gt;

&lt;p&gt;Why Alpine? Alpine Linux is extremely small (~5MB) compared to full OS images&lt;/p&gt;

&lt;p&gt;Why Node 16? Stable LTS version with long-term support&lt;/p&gt;

&lt;p&gt;Benefit: Smaller image = faster downloads, less disk space, better security&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Working Directory: WORKDIR /app&lt;/p&gt;

&lt;p&gt;Creates and sets /app as the working directory&lt;/p&gt;

&lt;p&gt;All subsequent commands run from this location&lt;/p&gt;

&lt;p&gt;Best practice: Always set a working directory to avoid confusion&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dependency Installation&lt;br&gt;
dockerfile&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;COPY package.json .&lt;br&gt;
RUN npm install&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First copies only package.json (not the entire code)

Installs dependencies

Optimization: Docker caches this layer. If package.json doesn't change, this step uses cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Copy Application Code: COPY . .&lt;/p&gt;

&lt;p&gt;Copies the rest of the application files&lt;/p&gt;

&lt;p&gt;Happens after dependency installation to leverage Docker caching&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Expose Port: EXPOSE 3100&lt;/p&gt;

&lt;p&gt;Documents that the container listens on port 3100&lt;/p&gt;

&lt;p&gt;Note: This is documentation only; actual port mapping happens in docker-compose&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start Command: CMD ["node", "server.js"]&lt;/p&gt;

&lt;p&gt;Defines the command to run when container starts&lt;/p&gt;

&lt;p&gt;Starts the Node.js server&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Frontend Dockerfile&lt;br&gt;
dockerfile&lt;/p&gt;

&lt;p&gt;FROM nginx:alpine&lt;br&gt;
WORKDIR /usr/share/nginx/html&lt;br&gt;
COPY . .&lt;br&gt;
EXPOSE 80&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Uses official Nginx image

Copies static files to Nginx's serving directory

Nginx automatically starts (no CMD needed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🔧 Docker Compose Breakdown&lt;br&gt;
Complete docker-compose.yml&lt;br&gt;
yaml&lt;/p&gt;

&lt;p&gt;version: '3.8'&lt;/p&gt;

&lt;h1&gt;
  
  
  Custom Network Definition
&lt;/h1&gt;

&lt;p&gt;networks:&lt;br&gt;
  mentees-net:&lt;br&gt;
    driver: bridge&lt;/p&gt;

&lt;h1&gt;
  
  
  Persistent Volume Definition
&lt;/h1&gt;

&lt;p&gt;volumes:&lt;br&gt;
  postgres_data:&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  # Database Service&lt;br&gt;
  db:&lt;br&gt;
    image: postgres:13-alpine&lt;br&gt;
    container_name: parent_db&lt;br&gt;
    restart: unless-stopped&lt;br&gt;
    networks:&lt;br&gt;
      - mentees-net&lt;br&gt;
    volumes:&lt;br&gt;
      - postgres_data:/var/lib/postgresql/data&lt;br&gt;
    environment:&lt;br&gt;
      POSTGRES_USER: ${DB_USER}&lt;br&gt;
      POSTGRES_PASSWORD: ${DB_PASSWORD}&lt;br&gt;
      POSTGRES_DB: ${DB_NAME}&lt;br&gt;
    ports:&lt;br&gt;
      - "5432:5432"&lt;/p&gt;

&lt;p&gt;# Backend API Service&lt;br&gt;
  backend:&lt;br&gt;
    build: ./backend&lt;br&gt;
    container_name: parent_backend&lt;br&gt;
    restart: unless-stopped&lt;br&gt;
    networks:&lt;br&gt;
      - mentees-net&lt;br&gt;
    depends_on:&lt;br&gt;
      - db&lt;br&gt;
    environment:&lt;br&gt;
      DB_HOST: db&lt;br&gt;
      DB_USER: ${DB_USER}&lt;br&gt;
      DB_PASSWORD: ${DB_PASSWORD}&lt;br&gt;
      DB_NAME: ${DB_NAME}&lt;br&gt;
    ports:&lt;br&gt;
      - "3100:3100"&lt;/p&gt;

&lt;p&gt;# Frontend Service&lt;br&gt;
  frontend:&lt;br&gt;
    build: ./frontend&lt;br&gt;
    container_name: parent_frontend&lt;br&gt;
    restart: unless-stopped&lt;br&gt;
    networks:&lt;br&gt;
      - mentees-net&lt;br&gt;
    depends_on:&lt;br&gt;
      - backend&lt;br&gt;
    ports:&lt;br&gt;
      - "8080:80"&lt;/p&gt;

&lt;p&gt;Key Components Explained:&lt;br&gt;
Version: '3.8'&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Specifies Docker Compose file format version

Version 3.8 supports all features needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Services Section&lt;br&gt;
Service Key Configuration   Purpose&lt;br&gt;
db  image: postgres:13-alpine   Uses pre-built PostgreSQL image&lt;br&gt;
backend build: ./backend    Builds from local Dockerfile&lt;br&gt;
frontend    build: ./frontend   Builds from local Dockerfile&lt;br&gt;
Restart Policy: restart: unless-stopped&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Automatically restarts containers if they crash

Won't restart if manually stopped

Ensures high availability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Depends On&lt;br&gt;
yaml&lt;/p&gt;

&lt;p&gt;backend:&lt;br&gt;
  depends_on:&lt;br&gt;
    - db&lt;/p&gt;

&lt;p&gt;frontend:&lt;br&gt;
  depends_on:&lt;br&gt;
    - backend&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ensures services start in correct order

Database starts before backend

Backend starts before frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Environment Variables&lt;br&gt;
yaml&lt;/p&gt;

&lt;p&gt;environment:&lt;br&gt;
  DB_HOST: db&lt;br&gt;
  DB_USER: ${DB_USER}&lt;br&gt;
  DB_PASSWORD: ${DB_PASSWORD}&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Uses variables from .env file

No hardcoded secrets

Service name db works as hostname (thanks to Docker DNS)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🌐 Volumes and Networking&lt;br&gt;
Volumes: Persistent Data Storage&lt;br&gt;
yaml&lt;/p&gt;

&lt;p&gt;volumes:&lt;br&gt;
  postgres_data:&lt;/p&gt;

&lt;p&gt;db:&lt;br&gt;
  volumes:&lt;br&gt;
    - postgres_data:/var/lib/postgresql/data&lt;/p&gt;

&lt;p&gt;What are Volumes?&lt;/p&gt;

&lt;p&gt;Volumes are Docker's mechanism for persisting data generated by containers.&lt;br&gt;
Why Volumes Matter:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Persistence: Data survives container restarts and removals

Performance: Volumes are faster than bind mounts

Backup/Restore: Easy to backup and migrate

Sharing: Can be shared between containers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;How It Works:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Volume postgres_data is defined

Mounted to /var/lib/postgresql/data (PostgreSQL's data directory)

Even if container is removed, data remains in volume

New container can mount same volume and access data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Networking: Service Communication&lt;br&gt;
yaml&lt;/p&gt;

&lt;p&gt;networks:&lt;br&gt;
  mentees-net:&lt;br&gt;
    driver: bridge&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  db:&lt;br&gt;
    networks:&lt;br&gt;
      - mentees-net&lt;br&gt;
  backend:&lt;br&gt;
    networks:&lt;br&gt;
      - mentees-net&lt;br&gt;
  frontend:&lt;br&gt;
    networks:&lt;br&gt;
      - mentees-net&lt;/p&gt;

&lt;p&gt;Custom Network Benefits:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Service Discovery: Containers can reach each other by service name

    Backend connects to db (not localhost or IP)

    No need to know IP addresses

Isolation: Services not exposed on host network unless mapped

    Database port 5432 is only exposed within the network

    External access only through mapped ports (3100, 8080)

DNS Resolution: Docker provides built-in DNS

    Container names resolve to IP addresses automatically
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Network Communication Flow:&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;Frontend (port 8080) ←→ Host Machine ←→ Backend (port 3100) ←→ Database (port 5432)&lt;br&gt;
         │                       │                           │&lt;br&gt;
         └───────── mentees-net ──┴───────── mentees-net ────┘&lt;/p&gt;

&lt;p&gt;Verify Network:&lt;br&gt;
bash&lt;/p&gt;

&lt;h1&gt;
  
  
  List networks
&lt;/h1&gt;

&lt;p&gt;docker network ls&lt;/p&gt;

&lt;h1&gt;
  
  
  Inspect custom network
&lt;/h1&gt;

&lt;p&gt;docker network inspect mentees-net&lt;/p&gt;

&lt;p&gt;🚧 Challenges Faced&lt;br&gt;
Challenge 1: Database Connection Timing&lt;/p&gt;

&lt;p&gt;Problem: Backend would start before database was ready, causing connection errors.&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;Error: connect ECONNREFUSED 172.18.0.2:5432&lt;/p&gt;

&lt;p&gt;Solution: Implemented retry logic in backend&lt;br&gt;
javascript&lt;/p&gt;

&lt;p&gt;const initDB = async () =&amp;gt; {&lt;br&gt;
  let retries = 5;&lt;br&gt;
  while (retries) {&lt;br&gt;
    try {&lt;br&gt;
      await pool.query('SELECT 1');&lt;br&gt;
      console.log('✅ Database connected');&lt;br&gt;
      return;&lt;br&gt;
    } catch (err) {&lt;br&gt;
      retries -= 1;&lt;br&gt;
      console.log(&lt;code&gt;⏳ Waiting for DB... (${retries} retries left)&lt;/code&gt;);&lt;br&gt;
      await new Promise(resolve =&amp;gt; setTimeout(resolve, 3000));&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;Challenge 2: Table Creation&lt;/p&gt;

&lt;p&gt;Problem: "relation 'registrations' does not exist" errors&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;ERROR: relation "registrations" does not exist&lt;/p&gt;

&lt;p&gt;Solution: Automatic table creation on startup with CREATE TABLE IF NOT EXISTS&lt;br&gt;
javascript&lt;/p&gt;

&lt;p&gt;await pool.query(&lt;code&gt;&lt;br&gt;
  CREATE TABLE IF NOT EXISTS registrations (&lt;br&gt;
    id SERIAL PRIMARY KEY,&lt;br&gt;
    parent_name VARCHAR(255) NOT NULL,&lt;br&gt;
    address TEXT NOT NULL,&lt;br&gt;
    phone VARCHAR(50) NOT NULL,&lt;br&gt;
    child_name VARCHAR(255) NOT NULL,&lt;br&gt;
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP&lt;br&gt;
  )&lt;br&gt;
&lt;/code&gt;);&lt;/p&gt;

&lt;p&gt;Challenge 3: CORS Issues&lt;/p&gt;

&lt;p&gt;Problem: Browser blocked requests from frontend to backend&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;Access to fetch at '&lt;a href="http://localhost:3100/register" rel="noopener noreferrer"&gt;http://localhost:3100/register&lt;/a&gt;' from origin '&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;' &lt;br&gt;
has been blocked by CORS policy&lt;/p&gt;

&lt;p&gt;Solution: Enabled CORS in backend&lt;br&gt;
javascript&lt;/p&gt;

&lt;p&gt;const cors = require('cors');&lt;br&gt;
app.use(cors());&lt;/p&gt;

&lt;p&gt;Challenge 4: Port Conflicts&lt;/p&gt;

&lt;p&gt;Problem: Port 3000 already in use on host machine&lt;/p&gt;

&lt;p&gt;Solution: Changed to port 3100 (within 3100-3111 range as required)&lt;br&gt;
yaml&lt;/p&gt;

&lt;p&gt;ports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"3100:3100"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Challenge 5: Environment Variables Not Loading&lt;/p&gt;

&lt;p&gt;Problem: Backend couldn't find database credentials&lt;/p&gt;

&lt;p&gt;Solution: Used .env file and Docker Compose environment substitution&lt;br&gt;
yaml&lt;/p&gt;

&lt;p&gt;environment:&lt;br&gt;
  DB_USER: ${DB_USER}&lt;br&gt;
  DB_PASSWORD: ${DB_PASSWORD}&lt;/p&gt;

&lt;p&gt;💡 Lessons Learned&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Docker Caching is Powerful&lt;/p&gt;

&lt;p&gt;Order matters in Dockerfile&lt;/p&gt;

&lt;p&gt;Copy package.json before source code to leverage cache&lt;/p&gt;

&lt;p&gt;Reduced build time from 30 seconds to 5 seconds&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always Use .dockerignore&lt;br&gt;
dockerignore&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;node_modules&lt;br&gt;
.env&lt;br&gt;
.git&lt;br&gt;
*.log&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keeps images small (reduced from 500MB to 150MB)

Prevents secrets from being embedded in images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Service Discovery is Magical&lt;/p&gt;

&lt;p&gt;Using service names (db) instead of IP addresses&lt;/p&gt;

&lt;p&gt;Docker DNS resolves automatically&lt;/p&gt;

&lt;p&gt;Makes configuration portable&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Health Checks Improve Reliability&lt;br&gt;
yaml&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;healthcheck:&lt;br&gt;
  test: ["CMD-SHELL", "pg_isready -U postgres"]&lt;br&gt;
  interval: 10s&lt;br&gt;
  timeout: 5s&lt;br&gt;
  retries: 5&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ensures database is ready before backend starts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Environment Variables for Security&lt;/p&gt;

&lt;p&gt;Never hardcode credentials&lt;/p&gt;

&lt;p&gt;Use .env file (added to .gitignore)&lt;/p&gt;

&lt;p&gt;Keep secrets out of version control&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Volume Management is Critical&lt;br&gt;
bash&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Backup volume
&lt;/h1&gt;

&lt;p&gt;docker run --rm -v postgres_data:/source -v $(pwd):/backup alpine tar czf /backup/postgres_backup.tar.gz -C /source .&lt;/p&gt;

&lt;h1&gt;
  
  
  Restore volume
&lt;/h1&gt;

&lt;p&gt;docker run --rm -v postgres_data:/target -v $(pwd):/backup alpine tar xzf /backup/postgres_backup.tar.gz -C /target&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Volumes survive container removal

Easy to backup and restore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Logging is Your Friend
bash&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  View all logs
&lt;/h1&gt;

&lt;p&gt;docker-compose logs -f&lt;/p&gt;

&lt;h1&gt;
  
  
  View specific service
&lt;/h1&gt;

&lt;p&gt;docker logs parent_backend&lt;br&gt;
docker logs parent_db&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Invaluable for debugging

Shows exactly what's happening
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Network Inspection Helps
bash&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;docker network inspect mentees-net&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shows all connected containers

Displays IP addresses and configurations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🎉 Conclusion&lt;/p&gt;

&lt;p&gt;Building this Parent Registration Portal taught me the fundamentals of Docker containerization:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Isolation: Each service runs in its own container

Orchestration: Docker Compose manages multi-container applications

Persistence: Volumes keep data safe

Communication: Custom networks enable service discovery

Security: Environment variables keep secrets safe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The application is now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Portable: Run anywhere with Docker

✅ Scalable: Easy to add more instances

✅ Maintainable: Clear separation of concerns

✅ Production-ready: Persistent data, restart policies, logging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🔗 Links&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Repository

Docker Documentation

Node.js Official Image

PostgreSQL Official Image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;📸 Screenshots&lt;br&gt;
Running Containers&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;$ docker ps&lt;br&gt;
CONTAINER ID   IMAGE          PORTS                    NAMES&lt;br&gt;
abc123def456   nginx:alpine   0.0.0.0:8080-&amp;gt;80/tcp     parent_frontend&lt;br&gt;
def456ghi789   backend        0.0.0.0:3100-&amp;gt;3100/tcp   parent_backend&lt;br&gt;
ghi789jkl012   postgres:13    0.0.0.0:5432-&amp;gt;5432/tcp   parent_db&lt;/p&gt;

&lt;p&gt;Custom Network&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;$ docker network ls&lt;br&gt;
NETWORK ID     NAME              DRIVER    SCOPE&lt;br&gt;
xyz789abc123   mentees-net       bridge    local&lt;/p&gt;

&lt;p&gt;Volumes&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;$ docker volume ls&lt;br&gt;
DRIVER    VOLUME NAME&lt;br&gt;
local     parent-registration-portal_postgres_data&lt;/p&gt;

&lt;p&gt;Happy Containerizing! 🐳&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>webdev</category>
      <category>python</category>
    </item>
  </channel>
</rss>
