Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
# Part 5
* [CI/CD with GitHub Action](./week5/CICD_with_GitHub_Actions.md)


# Prat 6
* [Docker-Containers](./week6/Docker-Containers.md)
805 changes: 805 additions & 0 deletions week6/Docker-Containers.md

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions week6/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use the latest Node.js image as the base image
FROM node:latest

# Set the working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json (if present)
COPY package*.json ./

# Install the dependencies
RUN npm install

# Copy the rest of the application code
COPY . .

# Command to run the application
CMD ["npm", "start"]

# HEALTHCHECK instruction to monitor container health
HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -f http://localhost:3000 || exit 1
44 changes: 44 additions & 0 deletions week6/app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { Client } = require('pg');
const express = require('express');
const morgan = require('morgan');

const app = express();

const connectionString = process.env.DATABASE_URL || 'postgres://myuser:mypassword@db:5432/mydb';

const client = new Client({
connectionString: connectionString,
});

// Use morgan for HTTP request logging
app.use(morgan('combined'));

// Example route
app.get('/', (req, res) => {
res.send('Hello, World!');
});

// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

async function checkDatabaseConnection() {
console.log(`Attempting to connect to database...`);
try {
await client.connect();
console.log('Successfully connected to PostgreSQL database!');
// const res = await client.query('SELECT NOW()');
// console.log('Current time from DB:', res.rows[0].now);
} catch (err) {
console.error('Error connecting to PostgreSQL database:', err.stack);
process.exit(1);
}
// finally {
// await client.end();
// console.log('Database client disconnected.');
// }
}

checkDatabaseConnection();
14 changes: 14 additions & 0 deletions week6/app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "node-postgres-app",
"version": "1.0.0",
"description": "Node.js app connecting to PostgreSQL in Docker",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"pg": "^8.0.0",
"express": "^4.18.2",
"morgan": "^1.10.0"
}
}
48 changes: 48 additions & 0 deletions week6/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
version: '3.8'

services:
web:
build: ./app
ports:
- "8082:3000" # Changed host port from 8080 to 8081
environment:
- DATABASE_URL=postgres://myuser:mypassword@db:5432/mydatabase
# NODE_ENV: development # Example: if your app uses NODE_ENV
depends_on:
- db
networks:
- app-net

db:
image: postgres:latest
restart: always # Ensures the DB service attempts to restart if it fails
environment:
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
- POSTGRES_DB=mydatabase
volumes:
- pgdata:/var/lib/postgresql/data # Persist database data in a named volume
networks:
- app-net
ports: # Optional: Expose PostgreSQL port to host for external tools (e.g., pgAdmin)
- "5432:5432" # Be cautious with exposing DB ports directly in production

slack-notifier:
build: ./slack-notifier
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro # Mount Docker socket read-only
environment:
SLACK_WEBHOOK_URL: "https://hooks.slack.com/services/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"
logging: # Optional: configure logging for the notifier itself
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

networks:
app-net:
driver: bridge

volumes:
pgdata: # Defines the named volume for data persistence
22 changes: 22 additions & 0 deletions week6/hello-docker-app/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

# Exclude node_modules, as dependencies will be installed inside the container
node_modules
npm-debug.log

# Exclude build artifacts or local development files
build
dist
.env

# Exclude version control directories and files
.git
.gitignore
.gitattributes

# Exclude Docker related files if they are in the context but not needed in image
Dockerfile
.dockerignore

# Exclude OS-specific files
.DS_Store
Thumbs.db
27 changes: 27 additions & 0 deletions week6/hello-docker-app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use the latest Node.js image as the base image
FROM node:latest


# Set the working directory inside the container
WORKDIR /app


# Copy package.json and package-lock.json to the working directory
COPY package*.json ./


# Install the dependencies
RUN npm install


# Copy the rest of the application code to the working directory
COPY . .


# Expose the port the app runs on
EXPOSE 3000



# Command to run the application
CMD ["npm", "start"]
5 changes: 5 additions & 0 deletions week6/hello-docker-app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@



console.log("Hello from Docker");

11 changes: 11 additions & 0 deletions week6/hello-docker-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "hello-docker-app",
"version": "1.0.0",
"description": "Simple Node.js app for Docker",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"author": "",
"license": "ISC"
}
13 changes: 13 additions & 0 deletions week6/slack-notifier/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM alpine:latest

# Install curl for sending notifications, jq for parsing JSON, and docker-cli for 'docker events'
RUN apk add --no-cache curl jq docker-cli

# Copy the notification script into the container
COPY notify.sh /usr/local/bin/notify.sh

# Make the script executable
RUN chmod +x /usr/local/bin/notify.sh

# Set the command to run the script
CMD ["notify.sh"]
65 changes: 65 additions & 0 deletions week6/slack-notifier/notify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/bin/sh

# SLACK_WEBHOOK_URL is expected to be set as an environment variable

if [ -z "$SLACK_WEBHOOK_URL" ]; then
echo "Error: SLACK_WEBHOOK_URL environment variable is not set."
exit 1
fi

echo "Slack Notifier started. Monitoring Docker events..."

# Listen to Docker events. We are interested in container and health_status events.
# Filters: type=container, and specific events like start, stop, die, health_status
docker events --filter 'type=container' --filter 'event=start' --filter 'event=stop' --filter 'event=die' --filter 'event=health_status' --format '{{json .}}' | while read -r event_json
do
# Ensure event_json is not empty
if [ -z "$event_json" ]; then
continue
fi

event_type=$(echo "$event_json" | jq -r '.Type')
action=$(echo "$event_json" | jq -r '.Action')
container_id=$(echo "$event_json" | jq -r '.Actor.ID')
container_name=$(echo "$event_json" | jq -r '.Actor.Attributes.name')
image_name=$(echo "$event_json" | jq -r '.Actor.Attributes.image')

# Construct message based on event action
message=""
if echo "$action" | grep -q "health_status:"; then
health_status_val=$(echo "$action" | cut -d' ' -f2) # Extracts 'healthy' or 'unhealthy'
message="🏥 Docker Health: Container \`$container_name\` (ID: \`${container_id:0:12}\`, Image: \`$image_name\`) is now \`$health_status_val\`."
elif [ "$action" = "die" ]; then
exit_code=$(echo "$event_json" | jq -r '.Actor.Attributes.exitCode')
message="💀 Docker Alert: Container \`$container_name\` (ID: \`${container_id:0:12}\`, Image: \`$image_name\`) has died with exit code \`$exit_code\`."
elif [ "$action" = "stop" ]; then
message="🛑 Docker Info: Container \`$container_name\` (ID: \`${container_id:0:12}\`, Image: \`$image_name\`) has stopped."
elif [ "$action" = "start" ]; then
message="🚀 Docker Info: Container \`$container_name\` (ID: \`${container_id:0:12}\`, Image: \`$image_name\`) has started."
else
# Skip other events not explicitly handled
echo "Skipping event: $action for $container_name"
continue
fi

if [ -n "$message" ]; then
echo "Preparing to send to Slack: $message"
# Construct JSON payload for Slack
json_payload=$(jq -n --arg text "$message" '{text: $text}')

# Send to Slack
# Adding -m 60 to set a maximum time for the curl operation to 60 seconds
# Adding --retry 3 and --retry-delay 5 for resilience
curl_response=$(curl -s -X POST -H 'Content-type: application/json' --data "$json_payload" "$SLACK_WEBHOOK_URL" -m 60 --retry 3 --retry-delay 5)

# Check if curl command itself failed (e.g., network issue before HTTP response)
if [ $? -ne 0 ]; then
echo "Error: curl command failed to execute."
# Check if Slack API returned "ok"
elif echo "$curl_response" | grep -q "ok"; then
echo "Successfully sent notification to Slack."
else
echo "Error sending notification to Slack. Response: $curl_response"
fi
fi
done