About Us
+This website is powered by Node.js and Express, running in a containerized environment.
+It demonstrates how easy it is to deploy web applications with the Cloud Node Container.
+diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..638900e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,37 @@ +# Git files +.git +.gitignore + +# Documentation +README.md +CLAUDE.md + +# Local development files +user/ +instance_* +local-dev.sh + +# Logs +*.log +logs/ + +# Temporary files +tmp/ +temp/ +.tmp + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* \ No newline at end of file diff --git a/.gitea/workflows/build-push.yaml b/.gitea/workflows/build-push.yaml new file mode 100644 index 0000000..3aa9b3c --- /dev/null +++ b/.gitea/workflows/build-push.yaml @@ -0,0 +1,40 @@ +name: Cloud Node Container +run-name: ${{ gitea.actor }} pushed a change to main +on: + push: + branches: + - main + +jobs: + Build-and-Push: + runs-on: ubuntu-latest + strategy: + matrix: + nodever: [18, 20, 22] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Gitea + uses: docker/login-action@v3 + with: + registry: repo.anhonesthost.net + username: ${{ secrets.CI_USER }} + password: ${{ secrets.CI_TOKEN }} + + - name: Build and Push Image + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64 + push: true + build-args: | + NODEVER=${{ matrix.nodever }} + tags: | + repo.anhonesthost.net/cloud-hosting-platform/cnc:node${{ matrix.nodever }} + ${{ matrix.nodever == '20' && 'repo.anhonesthost.net/cloud-hosting-platform/cnc:latest' || '' }} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3dadc91 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,96 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Cloud Node Container (CNC) is a Docker-based Node.js hosting environment that supports multiple Node.js versions (18, 20, 22) with Nginx reverse proxy, designed for both local development and production deployment. + +## Common Development Commands + +### Local Development Setup +```bash +# Quick start with automated setup (creates helper scripts and default app) +./local-dev.sh -n local-dev + +# With specific Node.js version +./local-dev.sh -n myproject -a 22 # Node.js 22 + +# Helper scripts created by local-dev.sh: +./instance_start # Start container +./instance_stop # Stop container +./instance_logs # View container logs +./instance_shell # Access container shell +``` + +### Building and Testing +```bash +# Build container locally +docker build -t cloud-node-container:latest . + +# Build with specific Node.js version +docker build --build-arg NODEVER=22 -t cnc:node22 . + +# Run container manually +docker run -d -p 80:80 -p 443:443 \ + -e NODEVER=20 -e environment=DEV \ + -e uid=$(id -u) -e user=$(whoami) -e domain=localhost \ + -v"$(pwd)/user":/home/$(whoami) \ + --name test-container cloud-node-container:latest +``` + +### Server Deployment +- Production git directory: `/root/whp` +- After `git pull`, sync web files: `rsync -av web-files/ /docker/whp/web/` + +## Architecture and Key Components + +### Directory Structure +- `/scripts/` - Container setup scripts (entrypoint, Node.js installers, nginx config) +- `/configs/` - Nginx, PM2, and default application configurations +- User applications go in `/home/$user/app/` + +### Container Behavior +1. **Entrypoint Flow** (`scripts/entrypoint.sh`): + - Creates user with specified UID + - Sets up directory structure (/home/$user/app/, logs/) + - Configures Nginx reverse proxy based on environment variables + - In DEV mode: starts Memcached for session storage + - Starts Nginx and PM2-managed Node.js application + +2. **Environment Modes**: + - **DEV** (`environment=DEV`): Local Memcached, automatic backups, verbose logging + - **PROD** (default): Expects external services, optimized for production + +3. **Node.js Version Management**: + - Controlled via `NODEVER` environment variable (18, 20, 22) + - Each version has dedicated install script in `/scripts/` + - PM2 ecosystem configuration adapts to user paths + +### Key Environment Variables +- `uid` (required): User ID for file permissions +- `user` (required): Username for container user +- `domain` (required): Primary domain for Nginx configuration +- `serveralias`: Additional domains (comma-separated) +- `NODEVER`: Node.js version to use (default: 20) +- `environment`: DEV or PROD mode + +## Important Technical Details + +1. **Health Check**: Available at `/ping` endpoint (Node.js app must implement) +2. **Logs Location**: `/home/$user/logs/nginx/` and `/home/$user/logs/nodejs/` +3. **Application Backups** (DEV mode): Every 30 minutes to `/home/$user/_backups/` +4. **Log Rotation**: Compress after 3 days, delete after 7 days +5. **SSL**: Self-signed certificate auto-generated, Nginx handles SSL termination +6. **Static Files**: Nginx serves from `/home/$user/app/public/` at `/static/` path +7. **Process Management**: PM2 handles Node.js application lifecycle + +## Application Requirements + +User applications in `/home/$user/app/` should include: +- `package.json` with dependencies and scripts +- Main application file (default: `index.js`) +- Optional: `ecosystem.config.js` for PM2 configuration +- Optional: `public/` directory for static assets + +The container provides a default Express.js application with health endpoints and Memcached session support if no user application is found. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bbd853d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +FROM almalinux/9-base +ARG NODEVER=20 + +# Install repos, update, install only needed packages, clean up in one layer +RUN dnf install -y \ + https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm && \ + dnf update -y && \ + dnf install -y wget procps cronie iproute nginx openssl curl && \ + dnf clean all && \ + rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/* \ + /var/cache/yum /tmp/* /var/tmp/* + +# Copy scripts into the image and set permissions +COPY ./scripts/ /scripts/ +RUN chmod +x /scripts/* + +# Generate self-signed cert, create needed dirs, install Node.js, clean up +RUN openssl req -newkey rsa:2048 -nodes -keyout /etc/pki/tls/private/localhost.key -x509 -days 3650 -subj "/CN=localhost" -out /etc/pki/tls/certs/localhost.crt && \ + mkdir -p /var/log/nodejs && \ + /scripts/install-node$NODEVER.sh && \ + rm -rf /tmp/* + +# Install PM2 globally for process management with minimal footprint +RUN npm install -g pm2@latest --production && \ + npm cache clean --force && \ + rm -rf /tmp/* + +# Copy configs and web files +COPY ./configs/nginx.conf /etc/nginx/nginx.conf +COPY ./configs/default.conf /etc/nginx/conf.d/default.conf +COPY ./configs/index.js /var/www/html/ +COPY ./configs/package.json /var/www/html/ +COPY ./configs/ecosystem.config.js /var/www/html/ + +# Set up cron job for log rotation +RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab + +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:3000/ping || exit 1 + +ENTRYPOINT [ "/scripts/entrypoint.sh" ] \ No newline at end of file diff --git a/MEMORY-GUIDE.md b/MEMORY-GUIDE.md new file mode 100644 index 0000000..dc4df44 --- /dev/null +++ b/MEMORY-GUIDE.md @@ -0,0 +1,229 @@ +# Memory Usage Guide - Cloud Node Container + +## Memory Requirements & Recommendations + +### Minimum RAM Requirements + +| Configuration | Minimum RAM | Recommended RAM | Use Case | +|---------------|-------------|-----------------|----------| +| **Production (Basic)** | 256MB | 512MB | Simple websites, APIs with <100 concurrent users | +| **Production (Standard)** | 512MB | 1GB | Standard web applications, APIs with moderate traffic | +| **Development** | 512MB | 1GB | Local development with all services (Memcached, logging) | +| **High Traffic** | 1GB | 2GB+ | Applications with >500 concurrent users or heavy processing | + +### Memory Breakdown (Optimized Configuration) + +| Component | Memory Usage | Notes | +|-----------|--------------|-------| +| **Base AlmaLinux 9** | ~80-120MB | Minimal base system | +| **Node.js Runtime** | ~30-50MB | V8 JavaScript engine | +| **PM2 Process Manager** | ~15-25MB | Process monitoring and management | +| **Nginx (Optimized)** | ~8-15MB | Single worker, limited buffers | +| **User Application** | ~50-200MB | Depends on application complexity | +| **Memcached (DEV mode)** | ~10-15MB | 32MB memory limit, actual usage varies | +| **System Overhead** | ~30-50MB | Logging, cron, system processes | + +**Total Typical Usage:** 220-480MB + +## Memory Optimizations Applied + +### Container-Level Optimizations + +1. **Node.js Heap Limit**: Set to 200MB via `--max-old-space-size=200` +2. **PM2 Memory Restart**: Applications restart at 256MB usage +3. **Memcached Limit**: 32MB maximum cache size +4. **Nginx Workers**: Single worker process for memory efficiency +5. **Buffer Limits**: Reduced client buffer sizes +6. **Log Buffering**: 2-minute flush intervals to reduce I/O + +### Application-Level Recommendations + +```javascript +// Memory-efficient application practices +const express = require('express'); +const app = express(); + +// Use compression to reduce memory for responses +app.use(require('compression')()); + +// Limit request size +app.use(express.json({ limit: '1mb' })); +app.use(express.urlencoded({ limit: '1mb', extended: true })); + +// Stream large files instead of loading into memory +const fs = require('fs'); +app.get('/large-file', (req, res) => { + const stream = fs.createReadStream('large-file.pdf'); + stream.pipe(res); +}); +``` + +## Memory Monitoring + +### Built-in Monitoring Scripts + +```bash +# Check current memory usage +docker exec container-name /scripts/memory-info.sh + +# Monitor PM2 processes +docker exec container-name pm2 monit + +# View memory usage over time +docker stats container-name +``` + +### Memory Alerts & Automatic Restarts + +The container includes automatic memory management: + +- **PM2 Restart**: Apps restart if they exceed 256MB +- **Health Checks**: Container monitors `/ping` endpoint +- **Process Recovery**: Failed processes automatically restart +- **Memory Leak Protection**: Regular restarts prevent memory buildup + +## Scaling Guidelines + +### When to Scale UP (More RAM): + +- PM2 shows frequent memory restarts +- Response times increase significantly +- Multiple applications in one container +- Heavy data processing or file handling +- High concurrent user count (>200 simultaneous) + +### When to Scale OUT (More Containers): + +- CPU usage consistently >80% +- Memory usage consistently >80% of allocation +- Need for geographic distribution +- Fault tolerance requirements + +### Memory-Efficient Application Patterns + +**Good Practices:** +```javascript +// Stream processing instead of loading entire files +const stream = require('stream'); + +// Use connection pooling for databases +const mysql = require('mysql2'); +const pool = mysql.createPool({ + host: 'localhost', + user: 'user', + password: 'password', + database: 'db', + connectionLimit: 5, // Limit connections + acquireTimeout: 60000, + timeout: 60000 +}); + +// Clean up intervals and timeouts +const cleanup = () => { + clearInterval(myInterval); + clearTimeout(myTimeout); +}; + +process.on('SIGTERM', cleanup); +process.on('SIGINT', cleanup); +``` + +**Avoid These Memory Patterns:** +```javascript +// DON'T: Store large amounts in memory +let globalCache = {}; // Can grow unbounded + +// DON'T: Create memory leaks with closures +setInterval(() => { + const largeData = new Array(1000000).fill('data'); + // largeData never gets garbage collected +}, 1000); + +// DON'T: Load entire files into memory +const fs = require('fs'); +const hugeFile = fs.readFileSync('huge-file.txt'); // Use streams instead +``` + +## Container Resource Limits + +### Setting Memory Limits + +```bash +# Limit container memory usage +docker run --memory=512m --memory-swap=512m your-container + +# Or in docker-compose.yml +services: + node-app: + mem_limit: 512m + memswap_limit: 512m +``` + +### Monitoring Memory Usage + +```bash +# Real-time memory stats +docker exec container-name cat /proc/meminfo + +# Process-specific memory +docker exec container-name ps aux --sort=-%mem + +# Application memory usage +docker exec container-name pm2 show app-name +``` + +## Troubleshooting Memory Issues + +### Common Memory Problems + +1. **Container OOM (Out of Memory)** + - **Symptom**: Container suddenly stops + - **Solution**: Increase container memory or optimize application + +2. **Application Memory Leaks** + - **Symptom**: Memory usage steadily increases + - **Solution**: Use PM2 memory restart feature, fix code leaks + +3. **Slow Performance** + - **Symptom**: High response times, high memory usage + - **Solution**: Enable compression, optimize database queries + +### Memory Debugging Tools + +```bash +# Inside container - check memory usage +free -h +cat /proc/meminfo +ps aux --sort=-%mem + +# PM2 memory monitoring +pm2 monit +pm2 show app-name + +# Node.js heap snapshots (development) +# Add to your app: +const v8 = require('v8'); +const fs = require('fs'); +const heapSnapshot = v8.writeHeapSnapshot(); +console.log('Heap snapshot written to', heapSnapshot); +``` + +## Production Memory Recommendations + +### Small Applications (< 50 concurrent users) +- **Container RAM**: 512MB +- **Node.js Heap**: 200MB (default) +- **Expected Usage**: 250-350MB + +### Medium Applications (50-200 concurrent users) +- **Container RAM**: 1GB +- **Node.js Heap**: 400MB (`--max-old-space-size=400`) +- **Expected Usage**: 500-700MB + +### Large Applications (200+ concurrent users) +- **Container RAM**: 2GB+ +- **Node.js Heap**: 800MB+ (`--max-old-space-size=800`) +- **Expected Usage**: 1-1.5GB +- **Consider**: Multiple container instances with load balancing + +The container is optimized for efficient memory usage while maintaining good performance and reliability. \ No newline at end of file diff --git a/README.md b/README.md index deecbcc..21536e8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,246 @@ -# cloud-node-container +# Cloud Node Container +This is a base container for running Node.js applications, supporting multiple Node.js versions (18, 20, 22). The default is Node.js 20. The container is based on AlmaLinux 9 and uses Nginx as a reverse proxy with SSL. It is designed for both development and production use. + +**You must have Docker or compatible containerization software running.** + +--- + +## What's Included? + +- **Multiple Node.js Versions:** 18, 20, 22 (set with `NODEVER` or `-a` flag) +- **PM2 Process Manager:** For production-grade Node.js application management +- **Nginx Reverse Proxy:** SSL-enabled reverse proxy with automatic HTTP to HTTPS redirect +- **Development Features:** Memcached for sessions, automatic backups, log rotation +- **Health Monitoring:** Built-in `/ping` endpoint for health checks + +--- + +## Quick Start: Local Development with `local-dev.sh` + +The easiest way to start a local development environment is with the provided `local-dev.sh` script. This script automates container setup, volume creation, log directories, and creates a sample Node.js application. + +### Usage Example + +```bash +./local-dev.sh -n local-dev +``` + +**Flags:** +- `-n` Name of the container (required) +- `-p` HTTP port (default: 80) +- `-s` HTTPS port (default: 443) +- `-r` Root path for files (default: current directory) +- `-a` Node.js version (default: 20; options: 18, 20, 22) +- `-v` Enable verbose mode +- `-h` Show help + +The script will: +- Create a user directory and log folders +- Create a default Node.js Express application if none exists +- Start the container with the correct environment variables +- Generate helper scripts in your root path: + - `instance_start` – Start the container + - `instance_stop` – Stop the container + - `instance_logs` – View container logs + - `instance_shell` – Access container shell + +--- + +## Manual Docker Usage + +You can also run the container manually: + +```bash +mkdir -p local-development/domain.tld +cd local-development/domain.tld +mkdir user +mkdir -p user/{app,logs/{nginx,nodejs}} +docker run -d -p 80:80 -p 443:443 -e NODEVER=20 -e environment=DEV --mount type=bind,source="$(pwd)"/user,target=/home/$(whoami) -e uid=$(id -u) -e user=$(whoami) -e domain=localhost --name local-dev cloud-node-container:latest +``` + +--- + +## Accessing the Container + +```bash +docker exec -it local-dev /bin/bash +``` + +--- + +## For Users: How to Deploy Your Application + +### Step 1: Prepare Your Node.js Application + +Your Node.js application needs just two files to get started: + +**Required Files:** +- `package.json` - Describes your app and its dependencies +- A main JavaScript file (usually `server.js` or `index.js`) + +**Optional Files:** +- `public/` folder for static files (HTML, CSS, images) +- `ecosystem.config.js` for advanced PM2 configuration + +### Step 2: What Users Need to Do + +**If you're not familiar with Node.js, here's what you need to know:** + +1. **Create a `package.json` file** - This tells Node.js about your app: +```json +{ + "name": "my-website", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} +``` + +2. **Create your main server file** - This runs your application: +```javascript +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; + +app.use(express.static('public')); // Serve static files + +app.get('/', (req, res) => { + res.send('
This is running on Node.js
+ About Page + `); +}); + +// Route for about page +app.get('/about', (req, res) => { + res.send(` +This website is powered by Node.js and Express
+ Back to Home + `); +}); + +// Health check (required by container) +app.get('/ping', (req, res) => { + res.json({ status: 'ok', timestamp: new Date() }); +}); + +// Start the server +app.listen(port, () => { + console.log(`Server running on port ${port}`); +}); +``` + +### 2. Start Your Container + +```bash +./local-dev.sh -n my-first-app +``` + +### 3. Access Your Site + +Visit `https://localhost` (accept the SSL warning) and you'll see your website! + +## Common Use Cases + +### Static Website (Like HTML/CSS sites) + +Put your HTML files in `user/app/public/` and use this simple server: + +```javascript +const express = require('express'); +const app = express(); + +app.use(express.static('public')); +app.get('/ping', (req, res) => res.json({ status: 'ok' })); + +app.listen(3000, () => console.log('Static site running')); +``` + +### Form Processing (Like PHP forms) + +```javascript +const express = require('express'); +const app = express(); + +app.use(express.urlencoded({ extended: true })); // Process form data +app.use(express.static('public')); + +// Show contact form +app.get('/contact', (req, res) => { + res.send(` + + `); +}); + +// Process form submission +app.post('/contact', (req, res) => { + const { name, email, message } = req.body; + + // Here you would save to database, send email, etc. + console.log('Contact form:', { name, email, message }); + + res.send(`We received your message.
`); +}); + +app.get('/ping', (req, res) => res.json({ status: 'ok' })); +app.listen(3000); +``` + +### Database Connection (Like MySQL in PHP) + +```javascript +const express = require('express'); +const app = express(); + +// In package.json dependencies, add: "mysql2": "^3.6.0" +const mysql = require('mysql2'); + +const db = mysql.createConnection({ + host: 'localhost', + user: 'your_user', + password: 'your_password', + database: 'your_database' +}); + +app.get('/users', (req, res) => { + db.query('SELECT * FROM users', (err, results) => { + if (err) { + res.status(500).json({ error: err.message }); + } else { + res.json(results); + } + }); +}); + +app.get('/ping', (req, res) => res.json({ status: 'ok' })); +app.listen(3000); +``` + +## File Structure Examples + +### Simple Website +``` +user/app/ +├── package.json +├── server.js +└── public/ + ├── index.html + ├── about.html + ├── style.css + └── images/ + └── logo.png +``` + +### API Application +``` +user/app/ +├── package.json +├── server.js +├── routes/ +│ ├── users.js +│ └── products.js +└── public/ + └── api-docs.html +``` + +## Key Differences from PHP + +| Concept | PHP | Node.js | +|---------|-----|---------| +| **File serving** | Apache serves .php files directly | Express routes handle requests | +| **Variables** | `$variable` | `let variable` | +| **Arrays** | `$arr = array()` | `let arr = []` | +| **Echo/Print** | `echo "Hello"` | `res.send("Hello")` | +| **Include files** | `include 'file.php'` | `require('./file.js')` | +| **Database** | Connect in each file | Connect once, reuse connection | +| **Sessions** | `$_SESSION` | `req.session` (with middleware) | +| **Forms** | `$_POST` | `req.body` (with middleware) | + +## Troubleshooting + +### Common Issues for PHP Users + +**Issue:** "Cannot GET /" error +**Solution:** Make sure you have an `app.get('/', ...)` route defined + +**Issue:** Static files (CSS/images) not loading +**Solution:** Put them in `public/` folder and add `app.use(express.static('public'))` + +**Issue:** Forms not working +**Solution:** Add `app.use(express.urlencoded({ extended: true }))` before your routes + +**Issue:** App crashes on restart +**Solution:** PM2 will automatically restart it, but check logs in `user/logs/nodejs/` + +### Getting Help + +- Check container logs: `./instance_logs` +- Check your app specifically: `docker exec -it your-container-name pm2 logs` +- Access container shell: `./instance_shell` +- Health check: Visit `/ping` to see if your app is responding + +## Advanced Features + +### Using Sessions (like PHP $_SESSION) + +```javascript +// Add to package.json dependencies: "express-session": "^1.17.3" +const session = require('express-session'); + +app.use(session({ + secret: 'your-secret-key', + resave: false, + saveUninitialized: false +})); + +app.get('/login', (req, res) => { + req.session.user = 'john_doe'; + res.send('Logged in!'); +}); + +app.get('/profile', (req, res) => { + if (req.session.user) { + res.send(`Hello ${req.session.user}!`); + } else { + res.send('Please log in'); + } +}); +``` + +### File Uploads + +```javascript +// Add to package.json: "multer": "^1.4.5-lts.1" +const multer = require('multer'); +const upload = multer({ dest: 'uploads/' }); + +app.post('/upload', upload.single('file'), (req, res) => { + res.send(`File uploaded: ${req.file.originalname}`); +}); +``` + +The container handles all the complex server setup, so you can focus on building your application! \ No newline at end of file diff --git a/configs/ecosystem.config.js b/configs/ecosystem.config.js new file mode 100644 index 0000000..2311a04 --- /dev/null +++ b/configs/ecosystem.config.js @@ -0,0 +1,31 @@ +module.exports = { + apps: [{ + name: 'node-app', + script: 'index.js', + instances: 1, + autorestart: true, + watch: false, + max_memory_restart: '256M', // Restart if app uses more than 256MB + kill_timeout: 3000, + wait_ready: true, + listen_timeout: 3000, + env: { + NODE_ENV: 'development', + PORT: 3000, + NODE_OPTIONS: '--max-old-space-size=200' // Limit V8 heap to 200MB + }, + env_production: { + NODE_ENV: 'production', + PORT: 3000, + NODE_OPTIONS: '--max-old-space-size=200' + }, + log_file: '/home/myuser/logs/nodejs/app.log', + error_file: '/home/myuser/logs/nodejs/error.log', + out_file: '/home/myuser/logs/nodejs/out.log', + log_date_format: 'YYYY-MM-DD HH:mm:ss Z', + log_type: 'json', + merge_logs: true, + max_restarts: 5, + min_uptime: '10s' + }] +}; \ No newline at end of file diff --git a/configs/index.js b/configs/index.js new file mode 100644 index 0000000..63efbb7 --- /dev/null +++ b/configs/index.js @@ -0,0 +1,101 @@ +const express = require('express'); +const session = require('express-session'); +const app = express(); +const port = process.env.PORT || 3000; + +// Middleware +app.use(express.json()); +app.use(express.static('public')); + +// Session configuration with Memcache (only in DEV mode when memcached is available) +if (process.env.NODE_ENV !== 'production') { + try { + const MemcachedStore = require('connect-memcached')(session); + app.use(session({ + store: new MemcachedStore({ + hosts: ['localhost:11211'] + }), + secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-production', + resave: false, + saveUninitialized: false, + cookie: { + secure: false, // Allow HTTP in development + maxAge: 24 * 60 * 60 * 1000 // 24 hours + } + })); + console.log('Memcached session store initialized'); + } catch (err) { + console.log('Memcached not available, using memory store'); + app.use(session({ + secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-production', + resave: false, + saveUninitialized: false, + cookie: { + secure: false, + maxAge: 24 * 60 * 60 * 1000 + } + })); + } +} else { + // Production session configuration (expects external session store) + app.use(session({ + secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-production', + resave: false, + saveUninitialized: false, + cookie: { + secure: true, // HTTPS only in production + maxAge: 24 * 60 * 60 * 1000 + } + })); +} + +// Health check endpoint +app.get('/ping', (req, res) => { + res.json({ + status: 'ok', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + version: process.env.npm_package_version || '1.0.0' + }); +}); + +// Default route +app.get('/', (req, res) => { + res.json({ + message: 'Cloud Node Container is running!', + nodeVersion: process.version, + environment: process.env.NODE_ENV || 'development', + timestamp: new Date().toISOString() + }); +}); + +// Info endpoint +app.get('/info', (req, res) => { + res.json({ + nodeVersion: process.version, + platform: process.platform, + arch: process.arch, + uptime: process.uptime(), + memory: process.memoryUsage(), + env: process.env.NODE_ENV || 'development' + }); +}); + +// Session demo endpoint +app.get('/session', (req, res) => { + if (!req.session.visits) { + req.session.visits = 0; + } + req.session.visits++; + + res.json({ + sessionId: req.sessionID, + visits: req.session.visits, + message: 'Session is working with ' + (process.env.NODE_ENV !== 'production' ? 'Memcached' : 'default store') + }); +}); + +app.listen(port, () => { + console.log(`Server running on port ${port}`); + console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); +}); \ No newline at end of file diff --git a/configs/nginx.conf b/configs/nginx.conf new file mode 100644 index 0000000..b5fc935 --- /dev/null +++ b/configs/nginx.conf @@ -0,0 +1,45 @@ +user nginx; +worker_processes 1; # Single worker for memory efficiency +worker_rlimit_nofile 1024; +error_log /var/log/nginx/error.log warn; # Less verbose logging +pid /run/nginx.pid; + +events { + worker_connections 512; # Reduced for memory efficiency + use epoll; + multi_accept on; +} + +http { + # Memory-optimized settings + client_body_buffer_size 16k; + client_header_buffer_size 1k; + client_max_body_size 8m; + large_client_header_buffers 2 1k; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main buffer=16k flush=2m; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 30; # Reduced from 65 + keepalive_requests 100; + types_hash_max_size 1024; # Reduced from 2048 + server_tokens off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Optimized gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_comp_level 2; # Lower compression for less CPU/memory usage + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + include /etc/nginx/conf.d/*.conf; +} \ No newline at end of file diff --git a/configs/package.json b/configs/package.json new file mode 100644 index 0000000..6c72df8 --- /dev/null +++ b/configs/package.json @@ -0,0 +1,27 @@ +{ + "name": "cloud-node-container", + "version": "1.0.0", + "description": "Default Node.js application for Cloud Node Container", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "nodemon index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "express": "^4.18.2", + "express-session": "^1.17.3", + "connect-memcached": "^1.0.0" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "keywords": [ + "nodejs", + "express", + "container", + "docker" + ], + "author": "", + "license": "MIT" +} \ No newline at end of file diff --git a/examples/api-server/package.json b/examples/api-server/package.json new file mode 100644 index 0000000..0cf1623 --- /dev/null +++ b/examples/api-server/package.json @@ -0,0 +1,12 @@ +{ + "name": "simple-api", + "version": "1.0.0", + "description": "A simple REST API server", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} \ No newline at end of file diff --git a/examples/api-server/server.js b/examples/api-server/server.js new file mode 100644 index 0000000..c94a38b --- /dev/null +++ b/examples/api-server/server.js @@ -0,0 +1,83 @@ +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; + +// Middleware +app.use(express.json()); + +// In-memory data store (in production, you'd use a database) +let users = [ + { id: 1, name: 'John Doe', email: 'john@example.com' }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com' } +]; +let nextId = 3; + +// Health check endpoint +app.get('/ping', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +// API Routes +app.get('/api/users', (req, res) => { + res.json(users); +}); + +app.get('/api/users/:id', (req, res) => { + const user = users.find(u => u.id === parseInt(req.params.id)); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + res.json(user); +}); + +app.post('/api/users', (req, res) => { + const { name, email } = req.body; + if (!name || !email) { + return res.status(400).json({ error: 'Name and email are required' }); + } + + const user = { id: nextId++, name, email }; + users.push(user); + res.status(201).json(user); +}); + +app.put('/api/users/:id', (req, res) => { + const user = users.find(u => u.id === parseInt(req.params.id)); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + const { name, email } = req.body; + if (name) user.name = name; + if (email) user.email = email; + + res.json(user); +}); + +app.delete('/api/users/:id', (req, res) => { + const index = users.findIndex(u => u.id === parseInt(req.params.id)); + if (index === -1) { + return res.status(404).json({ error: 'User not found' }); + } + + users.splice(index, 1); + res.status(204).send(); +}); + +// Basic documentation endpoint +app.get('/', (req, res) => { + res.json({ + message: 'Simple API Server', + endpoints: { + 'GET /api/users': 'Get all users', + 'GET /api/users/:id': 'Get user by ID', + 'POST /api/users': 'Create new user', + 'PUT /api/users/:id': 'Update user by ID', + 'DELETE /api/users/:id': 'Delete user by ID' + } + }); +}); + +app.listen(port, () => { + console.log(`API server running on port ${port}`); +}); \ No newline at end of file diff --git a/examples/simple-website/package.json b/examples/simple-website/package.json new file mode 100644 index 0000000..6ed912c --- /dev/null +++ b/examples/simple-website/package.json @@ -0,0 +1,12 @@ +{ + "name": "simple-website", + "version": "1.0.0", + "description": "A simple website with static pages", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} \ No newline at end of file diff --git a/examples/simple-website/public/about.html b/examples/simple-website/public/about.html new file mode 100644 index 0000000..b54f307 --- /dev/null +++ b/examples/simple-website/public/about.html @@ -0,0 +1,22 @@ + + + + + +This website is powered by Node.js and Express, running in a containerized environment.
+It demonstrates how easy it is to deploy web applications with the Cloud Node Container.
+Get in touch with us!
+ +This is a simple website running on Node.js in a Docker container!
+Current time:
+