Software Engineering β€’ Module 5
Operating Systems
and Virtualization
Lesson 3 | Prof. Afonso BrandΓ£o
Processes & Threads
Memory Management
Docker Hub Images
Multi-Env Dockerfiles
Staged Docker Up
JPA Verification
Docker Compose
Infra as Docs
OS Fundamentals
The software layer between hardware and applications

Core Responsibilities

  • Process management β€” scheduling CPU time
  • Memory management β€” RAM allocation, virtual memory
  • File system β€” storage abstraction
  • I/O management β€” devices and drivers
  • Security β€” user permissions, isolation

Process vs Thread

Process: independent execution unit with its own memory space.

Thread: lightweight unit within a process, shares memory and can communicate faster, but introduces race conditions and synchronization concerns.
VMs vs Containers
Two approaches to isolation and portability

Virtual Machines

  • Full operating system per VM
  • Strong isolation through the hypervisor
  • Heavier disk and memory footprint
  • Good for strong environment separation

Containers

  • Share the host operating system kernel
  • Process-level isolation with namespaces and cgroups
  • Lightweight startup and deployment
  • Excellent for packaging application environments
Containers are not replacements for every VM use case. Production environments often combine both strategies.
Testing Smaller Machines
Docker can simulate constrained environments with RAM and vCPU limits

What Docker Controls

  • Memory limit to emulate low-RAM machines
  • CPU quota to emulate fewer vCPUs
  • cgroups enforce these limits on the host
  • Useful for dev, staging, CI, and troubleshooting

Why It Matters

Instead of testing only on your powerful laptop, you can ask: "Would this app still behave correctly on a 512 MB, half-core machine?"
Docker is not only for packaging apps. It also helps reproduce infrastructure constraints before production.
Resource Limit Commands
Simple flags for RAM and CPU-constrained tests

Examples

docker run --rm --memory="512m" --cpus="0.5" myapp:dev docker run --rm --memory="1g" --cpus="1.0" -p 8080:80 myapp:staging services: app: image: myapp:dev mem_limit: 512m cpus: 0.5
  • --memory caps available RAM for the container
  • --cpus limits the CPU time the container can consume
  • Compose can persist the same test profile for the whole team
What To Validate Under Limits
Behavior, resilience, and performance on constrained infrastructure

Observe

  • Slow startups and degraded response time
  • Out-of-memory crashes and restart loops
  • Excessive GC, queue buildup, or timeouts
  • Thread pools or workers competing for few CPU slices

Good Use Cases

  • Validate sizing assumptions before cloud deployment
  • Reproduce incidents seen only in small instances
  • Compare app behavior across dev, staging, and production profiles
  • Catch performance regressions earlier in CI
Docker in Practice
Build, ship, and run applications consistently

Core Concepts

  • Image β€” immutable template
  • Container β€” running instance
  • Registry β€” storage for images
  • Volume β€” persistence outside the container
  • Network β€” communication layer

Essential Commands

docker build -t myapp:1.0 . docker run -p 8080:80 myapp:1.0 docker ps docker stop <id> docker logs <id>
Writing a Dockerfile
Defining the runtime environment as code

TypeScript Example

FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build EXPOSE 3000 CMD ["node", "dist/main.js"]

Best Practices

  • Use smaller base images when possible
  • Copy dependency files before source code to leverage cache
  • Keep one main process per container
  • Never store secrets in the image
  • Use .dockerignore to reduce build context
Docker Hub: Image Types
Understanding image sources and trust levels

1. Official Images

Maintained by Docker and verified publishers. Highest trust level.

docker pull python:3.12-slim docker pull node:20-alpine docker pull postgres:16
βœ… Security patches, updated regularly, documented

2. Verified Publisher Images

From verified organizations on Docker Hub.

docker pull mysql/mysql-server:8.0 docker pull mongo/mongodb-community-server:latest
βœ“ Verified source, organization-maintained

3. Community Images

Created by users. Use with caution β€” verify before production.

docker pull someuser/custom-app:1.0
⚠️ Review Dockerfile, check stars/downloads, verify security
Multi-Environment Dockerfiles
Separate configurations for dev, staging, and production

Development Dockerfile

# Dockerfile.dev FROM node:20-alpine WORKDIR /app # Install dev dependencies COPY package*.json ./ RUN npm ci # Copy source for live editing COPY . . # Enable hot reload ENV NODE_ENV=development ENV DEBUG=true EXPOSE 3000 CMD ["npm", "run", "dev"]
🎯 Dev: Debug tools, hot reload (ts-node/nodemon), verbose logging, test dependencies
Staging Environment
Pre-production validation with realistic constraints

Staging Dockerfile

# Dockerfile.staging FROM node:20-alpine WORKDIR /app # Production dependencies only COPY package*.json ./ RUN npm ci --only=production # Copy pre-built application COPY dist ./dist # Staging-specific settings ENV NODE_ENV=staging ENV LOG_LEVEL=info ENV ENABLE_PROFILING=true EXPOSE 3000 CMD ["node", "dist/main.js"]
🎯 Staging: Production-like, profiling enabled, integration tests ready
Production Dockerfile
Optimized, secure, and minimal footprint

Multi-Stage Production Build

# Dockerfile.prod # Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: Runtime FROM node:20-alpine # Non-root user for security RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 WORKDIR /app # Copy only production dependencies and build COPY package*.json ./ RUN npm ci --only=production && npm cache clean --force COPY --from=builder /app/dist ./dist RUN chown -R nodejs:nodejs /app USER nodejs # Production settings ENV NODE_ENV=production ENV LOG_LEVEL=warn EXPOSE 3000 CMD ["node", "dist/main.js"]
🎯 Production: Multi-stage, non-root user, minimal image, optimized workers
Docker Up: Staged Workflow
Step-by-step verification before full deployment

Stage 1: Load & Build Verification

# Build and verify image creation docker build -f Dockerfile.dev -t myapp:dev . # Check image layers and size docker history myapp:dev docker images myapp:dev # Verify TypeScript compilation docker run --rm myapp:dev npm run build
βœ… Verify: Build succeeds, no TypeScript errors, expected image size
Stage 2: Container Startup Tests
Validate container initialization and health
# Run with health check docker run -d --name myapp-test \ -p 3000:3000 \ --health-cmd="curl -f http://localhost:3000/health || exit 1" \ --health-interval=10s \ --health-retries=3 \ myapp:dev # Check container status docker ps docker logs myapp-test # Test health endpoint curl http://localhost:3000/health
βœ… Verify: Container starts, health check passes, no crash loops
Stage 3: Business Rules & JPA Verification
Infrastructure requirements as business constraints

TypeORM / Prisma Infrastructure Checks

# Database connectivity test docker run -d --name postgres-db \ -e POSTGRES_DB=myapp \ -e POSTGRES_USER=admin \ -e POSTGRES_PASSWORD=secret \ -p 5432:5432 \ postgres:16 # Run migration verification (TypeORM) docker run --rm \ --link postgres-db:db \ -e DATABASE_URL=postgresql://admin:secret@db:5432/myapp \ myapp:dev npm run typeorm migration:run # Verify entity mappings (Prisma) docker run --rm \ -e DATABASE_URL=postgresql://admin:secret@db:5432/myapp \ myapp:dev npx prisma db pull # Validate schema sync docker run --rm \ -e DATABASE_URL=postgresql://admin:secret@db:5432/myapp \ myapp:dev npm run schema:validate
🎯 Verify: Database connection, migrations, entity mappings, relationship constraints
Business Rules Checklist
Infrastructure requirements tied to domain logic

Data Integrity

  • Unique constraints β€” emails, usernames
  • Foreign keys β€” referential integrity
  • Not null β€” required fields validation
  • Data types β€” correct column types

Transaction Management

  • ACID compliance β€” atomic operations
  • Rollback on failure β€” error handling
  • Isolation levels β€” concurrency control
  • Locking strategies β€” optimistic/pessimistic
# Run integration tests docker run --rm \ --link postgres-db:db \ -e DATABASE_URL=postgresql://admin:secret@db:5432/myapp \ myapp:dev npm run test:integration
Stage 4: Full Stack Verification
End-to-end testing in containerized environment

Docker Compose for Complete Stack

# docker-compose.test.yml version: '3.8' services: db: image: postgres:16 environment: POSTGRES_DB: myapp POSTGRES_USER: admin POSTGRES_PASSWORD: secret healthcheck: test: ["CMD-SHELL", "pg_isready -U admin"] interval: 10s retries: 5 app: build: context: . dockerfile: Dockerfile.dev ports: - "3000:3000" environment: DATABASE_URL: postgresql://admin:secret@db:5432/myapp depends_on: db: condition: service_healthy test: build: context: . dockerfile: Dockerfile.dev command: npm run test:e2e environment: DATABASE_URL: postgresql://admin:secret@db:5432/myapp depends_on: - db
🎯 Run: docker-compose -f docker-compose.test.yml up --abort-on-container-exit
Docker as Documentation
Infrastructure requirements as living documentation

Why Dockerize Early?

  • Requirement validation β€” Dockerfile exposes dependencies early
  • Environment consistency β€” dev = staging = production
  • Onboarding β€” new developers run docker-compose up
  • CI/CD readiness β€” same containers in pipelines
πŸ’‘ Docker is not just deployment β€” it's executable documentation of your infrastructure requirements and business constraints.
Questions?
Next: Security in Web and Cloud Systems