Complete Node.js container implementation with multi-version support
- Add Dockerfile with AlmaLinux 9 base, Nginx reverse proxy, and PM2 - Support Node.js versions 18, 20, 22 with automated installation - Implement memory-optimized configuration (256MB minimum, 512MB recommended) - Add Memcached session storage for development environments - Create comprehensive documentation (README, USER-GUIDE, MEMORY-GUIDE, CLAUDE.md) - Include example applications (simple website and REST API) - Add Gitea CI/CD pipeline for automated multi-version builds - Provide local development script with helper utilities - Implement health monitoring, log rotation, and backup systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
37
.dockerignore
Normal file
37
.dockerignore
Normal file
@@ -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*
|
40
.gitea/workflows/build-push.yaml
Normal file
40
.gitea/workflows/build-push.yaml
Normal file
@@ -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' || '' }}
|
96
CLAUDE.md
Normal file
96
CLAUDE.md
Normal file
@@ -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.
|
41
Dockerfile
Normal file
41
Dockerfile
Normal file
@@ -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" ]
|
229
MEMORY-GUIDE.md
Normal file
229
MEMORY-GUIDE.md
Normal file
@@ -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.
|
246
README.md
246
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('<h1>My Website is Running!</h1>');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/ping', (req, res) => {
|
||||||
|
res.json({ status: 'ok' }); // Health check for the container
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server running on port ${port}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Put your files in the right place**:
|
||||||
|
- Copy your `package.json` and `server.js` to `user/app/`
|
||||||
|
- Put HTML, CSS, images in `user/app/public/`
|
||||||
|
|
||||||
|
4. **Start the container**:
|
||||||
|
```bash
|
||||||
|
./local-dev.sh -n my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
**That's it!** The container will:
|
||||||
|
- Install your dependencies automatically
|
||||||
|
- Start your application with PM2
|
||||||
|
- Handle SSL and reverse proxy
|
||||||
|
- Provide health monitoring
|
||||||
|
|
||||||
|
### Step 3: Example Applications
|
||||||
|
|
||||||
|
See the `examples/` directory for complete working examples:
|
||||||
|
|
||||||
|
**Simple Website** (`examples/simple-website/`):
|
||||||
|
- Static HTML pages with navigation
|
||||||
|
- CSS styling
|
||||||
|
- Express.js server for routing
|
||||||
|
|
||||||
|
**API Server** (`examples/api-server/`):
|
||||||
|
- REST API with CRUD operations
|
||||||
|
- JSON responses
|
||||||
|
- In-memory data storage
|
||||||
|
|
||||||
|
### What Happens Automatically
|
||||||
|
|
||||||
|
When you place your app in `user/app/`, the container automatically:
|
||||||
|
1. Runs `npm install` to install dependencies
|
||||||
|
2. Starts your app with PM2 process manager
|
||||||
|
3. Sets up Nginx reverse proxy with SSL
|
||||||
|
4. Configures logging and health checks
|
||||||
|
5. Starts Memcached for session storage (in DEV mode)
|
||||||
|
|
||||||
|
### Troubleshooting for New Users
|
||||||
|
|
||||||
|
- **App won't start?** Check that your `package.json` has a valid "start" script
|
||||||
|
- **Can't access your site?** Make sure your app listens on port 3000 and has a `/ping` endpoint
|
||||||
|
- **Dependencies missing?** List all required packages in your `package.json` dependencies section
|
||||||
|
- **Static files not loading?** Put them in a `public/` folder and use `app.use(express.static('public'))`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Multiple Node.js Versions:** 18, 20, 22 (set with `NODEVER` environment variable)
|
||||||
|
- **Process Management:** PM2 for production-grade Node.js application management
|
||||||
|
- **Reverse Proxy:** Nginx handles SSL termination and proxies requests to Node.js
|
||||||
|
- **Automatic Backups:** Application files backed up every 30 minutes in DEV mode
|
||||||
|
- **Log Management:** Log rotation compresses logs older than 3 days, deletes after 7 days
|
||||||
|
- **Session Storage:** Memcached available in DEV mode for session management
|
||||||
|
- **SSL:** Self-signed certificate enabled by default with automatic HTTP→HTTPS redirect
|
||||||
|
- **Health Checks:** `/ping`, `/info` endpoints for monitoring
|
||||||
|
- **Static Files:** Nginx serves static files from `/home/$user/app/public/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
**Required:**
|
||||||
|
- `uid` – User ID for file permissions
|
||||||
|
- `user` – Username for file permissions
|
||||||
|
- `domain` – Primary domain for configuration
|
||||||
|
|
||||||
|
**Optional:**
|
||||||
|
- `environment` – Set to `DEV` to start Redis and enable development features
|
||||||
|
- `serveralias` – Comma-separated list of alternative hostnames
|
||||||
|
- `NODEVER` – Node.js version (18, 20, 22)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Default Application Endpoints
|
||||||
|
|
||||||
|
The default Express.js application provides:
|
||||||
|
- `/` – Basic application info and status
|
||||||
|
- `/ping` – Health check endpoint (JSON response)
|
||||||
|
- `/info` – Detailed system information
|
||||||
|
- `/session` – Session demo endpoint (shows Memcached sessions working)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Helpful Notes
|
||||||
|
|
||||||
|
- To restart the instance: `./instance_start` or `docker start {container-name}`
|
||||||
|
- To stop: `./instance_stop` or `docker stop {container-name}`
|
||||||
|
- To view logs: `./instance_logs` or `docker logs -f {container-name}`
|
||||||
|
- To access shell: `./instance_shell` or `docker exec -it {container-name} /bin/bash`
|
||||||
|
- To delete a container: `docker rm {container-name}` (does not delete user files)
|
||||||
|
- Application logs are in `/home/$user/logs/nodejs/`
|
||||||
|
- Nginx logs are in `/home/$user/logs/nginx/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System Requirements
|
||||||
|
|
||||||
|
### Memory Requirements
|
||||||
|
- **Minimum RAM**: 256MB (basic applications)
|
||||||
|
- **Recommended RAM**: 512MB (standard applications)
|
||||||
|
- **Development**: 512MB-1GB (with all services enabled)
|
||||||
|
|
||||||
|
The container is optimized for memory efficiency with automatic memory management and process restarts. See `MEMORY-GUIDE.md` for detailed memory optimization information.
|
||||||
|
|
||||||
|
### Performance Features
|
||||||
|
- Automatic application restart at 256MB memory usage
|
||||||
|
- V8 heap limited to 200MB by default
|
||||||
|
- Nginx optimized for single-worker, low-memory operation
|
||||||
|
- Memcached limited to 32MB cache size
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- The first run may take a few minutes as Node.js and dependencies are installed
|
||||||
|
- If you need to change Node.js version, stop and remove the container, then recreate with the desired version
|
||||||
|
- For custom applications, ensure your `package.json` has a valid start script
|
||||||
|
- Check `/home/$user/logs/nodejs/error.log` for application errors
|
||||||
|
- The health check endpoint `/ping` should return a 200 status when the application is running properly
|
||||||
|
- **Memory issues**: Run `/scripts/memory-info.sh` inside container to check memory usage
|
||||||
|
- **Process monitoring**: Use `pm2 monit` to watch application performance
|
275
USER-GUIDE.md
Normal file
275
USER-GUIDE.md
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
# User Guide: Deploying Applications with Cloud Node Container
|
||||||
|
|
||||||
|
## Quick Start (For Non-Node.js Users)
|
||||||
|
|
||||||
|
If you've never used Node.js before, don't worry! This container makes it simple.
|
||||||
|
|
||||||
|
### What You Need to Know
|
||||||
|
|
||||||
|
**Node.js** is like PHP, but for JavaScript. Instead of Apache serving PHP files, your JavaScript code runs as a server application.
|
||||||
|
|
||||||
|
**Express.js** is like a web framework (similar to Laravel for PHP) that makes it easy to handle web requests.
|
||||||
|
|
||||||
|
### Basic Workflow Comparison
|
||||||
|
|
||||||
|
| PHP/Apache Workflow | Node.js Container Workflow |
|
||||||
|
|---------------------|---------------------------|
|
||||||
|
| Put `.php` files in web directory | Put `.js` files in `user/app/` |
|
||||||
|
| Apache serves files directly | Express.js handles requests |
|
||||||
|
| Database connection in each file | Server runs continuously |
|
||||||
|
| `<?php echo "Hello"; ?>` | `res.send("Hello")` |
|
||||||
|
|
||||||
|
## Step-by-Step: Deploy Your First App
|
||||||
|
|
||||||
|
### 1. Create Your Application Files
|
||||||
|
|
||||||
|
Create these two files in your `user/app/` directory:
|
||||||
|
|
||||||
|
**File: `package.json`**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-first-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**File: `server.js`**
|
||||||
|
```javascript
|
||||||
|
const express = require('express');
|
||||||
|
const app = express();
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// Serve static files (HTML, CSS, images) from 'public' folder
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
|
// Route for home page
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.send(`
|
||||||
|
<h1>Welcome to My Website!</h1>
|
||||||
|
<p>This is running on Node.js</p>
|
||||||
|
<a href="/about">About Page</a>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route for about page
|
||||||
|
app.get('/about', (req, res) => {
|
||||||
|
res.send(`
|
||||||
|
<h1>About Us</h1>
|
||||||
|
<p>This website is powered by Node.js and Express</p>
|
||||||
|
<a href="/">Back to Home</a>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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(`
|
||||||
|
<form method="POST" action="/contact">
|
||||||
|
<input name="name" placeholder="Your Name" required>
|
||||||
|
<input name="email" type="email" placeholder="Email" required>
|
||||||
|
<textarea name="message" placeholder="Message" required></textarea>
|
||||||
|
<button type="submit">Send</button>
|
||||||
|
</form>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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(`<h1>Thanks ${name}!</h1><p>We received your message.</p>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
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!
|
31
configs/ecosystem.config.js
Normal file
31
configs/ecosystem.config.js
Normal file
@@ -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'
|
||||||
|
}]
|
||||||
|
};
|
101
configs/index.js
Normal file
101
configs/index.js
Normal file
@@ -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'}`);
|
||||||
|
});
|
45
configs/nginx.conf
Normal file
45
configs/nginx.conf
Normal file
@@ -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;
|
||||||
|
}
|
27
configs/package.json
Normal file
27
configs/package.json
Normal file
@@ -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"
|
||||||
|
}
|
12
examples/api-server/package.json
Normal file
12
examples/api-server/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
83
examples/api-server/server.js
Normal file
83
examples/api-server/server.js
Normal file
@@ -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}`);
|
||||||
|
});
|
12
examples/simple-website/package.json
Normal file
12
examples/simple-website/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
22
examples/simple-website/public/about.html
Normal file
22
examples/simple-website/public/about.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>About - My Simple Website</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/contact">Contact</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>About Us</h1>
|
||||||
|
<p>This website is powered by Node.js and Express, running in a containerized environment.</p>
|
||||||
|
<p>It demonstrates how easy it is to deploy web applications with the Cloud Node Container.</p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
33
examples/simple-website/public/contact.html
Normal file
33
examples/simple-website/public/contact.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Contact - My Simple Website</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/contact">Contact</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>Contact Us</h1>
|
||||||
|
<p>Get in touch with us!</p>
|
||||||
|
<form>
|
||||||
|
<label for="name">Name:</label>
|
||||||
|
<input type="text" id="name" name="name" required>
|
||||||
|
|
||||||
|
<label for="email">Email:</label>
|
||||||
|
<input type="email" id="email" name="email" required>
|
||||||
|
|
||||||
|
<label for="message">Message:</label>
|
||||||
|
<textarea id="message" name="message" required></textarea>
|
||||||
|
|
||||||
|
<button type="submit">Send Message</button>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
26
examples/simple-website/public/index.html
Normal file
26
examples/simple-website/public/index.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>My Simple Website</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/contact">Contact</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>Welcome to My Website</h1>
|
||||||
|
<p>This is a simple website running on Node.js in a Docker container!</p>
|
||||||
|
<p>Current time: <span id="time"></span></p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('time').textContent = new Date().toLocaleString();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
65
examples/simple-website/public/style.css
Normal file
65
examples/simple-website/public/style.css
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
background-color: #333;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-right: 1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover {
|
||||||
|
background-color: #555;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2rem;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
29
examples/simple-website/server.js
Normal file
29
examples/simple-website/server.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const path = require('path');
|
||||||
|
const app = express();
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// Serve static files from public directory
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
|
// Basic routes
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/about', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, 'public', 'about.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/contact', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, 'public', 'contact.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Health check for the container
|
||||||
|
app.get('/ping', (req, res) => {
|
||||||
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Simple website running on port ${port}`);
|
||||||
|
});
|
95
local-dev.sh
Executable file
95
local-dev.sh
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
name=''
|
||||||
|
http_port='80'
|
||||||
|
https_port='443'
|
||||||
|
root_path="$(pwd)"
|
||||||
|
verbose='false'
|
||||||
|
|
||||||
|
while getopts 'n:p:s:r:a:vh' flag; do
|
||||||
|
case "${flag}" in
|
||||||
|
n) name="${OPTARG}" ;;
|
||||||
|
p) http_port="${OPTARG}" ;;
|
||||||
|
s) https_port="${OPTARG}" ;;
|
||||||
|
r) root_path="${OPTARG}" ;;
|
||||||
|
a) nodever="${OPTARG}" ;;
|
||||||
|
v) verbose='true' ;;
|
||||||
|
h) echo "Variables"
|
||||||
|
echo "-n = Name of Container, Required"
|
||||||
|
echo "-p = Non-https Port Override, default 80"
|
||||||
|
echo "-s = Https Port Override, default 443"
|
||||||
|
echo "-r = Root Path for files, defaults to current working path"
|
||||||
|
echo "-a = Node.js Version, Default to 20 (options: 18, 20, 22)"
|
||||||
|
echo "-v = Enable Verbose Mode"
|
||||||
|
exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if Docker is installed
|
||||||
|
echo "Checking if Docker is Installed..."
|
||||||
|
check_docker=$(command -v docker)
|
||||||
|
if [ $? != 0 ]; then
|
||||||
|
echo "Docker must be installed to run this application"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Docker looks to be installed"
|
||||||
|
|
||||||
|
if [ -z "$name" ]; then
|
||||||
|
echo "Name not set, please set it with -n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$nodever" ]; then
|
||||||
|
nodever=20;
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Building Docker Image..."
|
||||||
|
user=$(whoami)
|
||||||
|
uid=$(id -u)
|
||||||
|
if [ ! -d "$root_path/user" ]; then
|
||||||
|
mkdir -p "$root_path/user";
|
||||||
|
mkdir -p "$root_path/user/logs/{nginx,nodejs}";
|
||||||
|
mkdir -p "$root_path/user/app";
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create default Node.js app if it doesn't exist
|
||||||
|
if [ ! -f "$root_path/user/app/package.json" ]; then
|
||||||
|
echo "Creating default Node.js application..."
|
||||||
|
cp configs/package.json "$root_path/user/app/"
|
||||||
|
cp configs/index.js "$root_path/user/app/"
|
||||||
|
cp configs/ecosystem.config.js "$root_path/user/app/"
|
||||||
|
# Update ecosystem config with correct user path
|
||||||
|
sed -i "s/\/home\/myuser/\/home\/$user/g" "$root_path/user/app/ecosystem.config.js"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$check_docker run --pull=always -d -p "$http_port":80 -p "$https_port":443 -e NODEVER=$nodever -e environment=DEV --mount type=bind,source="$root_path"/user,target=/home/"$user" --mount type=bind,source="$(pwd)"/user/logs/nginx,target=/var/log/nginx --mount type=bind,source="$(pwd)"/user/logs/nodejs,target=/var/log/nodejs -e uid="$uid" -e user="$user" -e domain="$name-local.dev" --name "$name" cloud-node-container:latest
|
||||||
|
|
||||||
|
echo "Creating management scripts in root directory..."
|
||||||
|
echo "#!/usr/bin/env bash" > "$root_path/instance_start"
|
||||||
|
echo "docker start $name" >> "$root_path/instance_start"
|
||||||
|
echo "#!/usr/bin/env bash" > "$root_path/instance_stop"
|
||||||
|
echo "docker stop $name" >> "$root_path/instance_stop"
|
||||||
|
echo "#!/usr/bin/env bash" > "$root_path/instance_logs"
|
||||||
|
echo "docker logs -f $name" >> "$root_path/instance_logs"
|
||||||
|
echo "#!/usr/bin/env bash" > "$root_path/instance_shell"
|
||||||
|
echo "docker exec -it $name /bin/bash" >> "$root_path/instance_shell"
|
||||||
|
chmod +x $root_path/instance_*
|
||||||
|
|
||||||
|
echo "Waiting 30 seconds for setup to finish..."
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
echo "Local Development Instance Created!"
|
||||||
|
echo "- Container name: $name"
|
||||||
|
echo "- HTTP port: $http_port"
|
||||||
|
echo "- HTTPS port: $https_port"
|
||||||
|
echo "- Node.js version: $nodever"
|
||||||
|
echo ""
|
||||||
|
echo "Management scripts created:"
|
||||||
|
echo " ./instance_start - Start the container"
|
||||||
|
echo " ./instance_stop - Stop the container"
|
||||||
|
echo " ./instance_logs - View container logs"
|
||||||
|
echo " ./instance_shell - Access container shell"
|
||||||
|
echo ""
|
||||||
|
echo "Your application files are in: $root_path/user/app/"
|
||||||
|
echo "Visit https://localhost:$https_port (accept SSL warning) to see your app"
|
||||||
|
|
||||||
|
exit 0
|
17
scripts/backup.sh
Executable file
17
scripts/backup.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
USER=$1
|
||||||
|
BACKUP_DIR="/home/$USER/_backups"
|
||||||
|
DATE=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# Create backup directory if it doesn't exist
|
||||||
|
mkdir -p $BACKUP_DIR
|
||||||
|
|
||||||
|
# Backup application files
|
||||||
|
tar -czf $BACKUP_DIR/app_backup_$DATE.tar.gz -C /home/$USER app/
|
||||||
|
|
||||||
|
# Keep only last 10 backups
|
||||||
|
cd $BACKUP_DIR
|
||||||
|
ls -t app_backup_*.tar.gz | tail -n +11 | xargs -r rm
|
||||||
|
|
||||||
|
echo "Backup completed: app_backup_$DATE.tar.gz"
|
54
scripts/create-nginx-config.sh
Executable file
54
scripts/create-nginx-config.sh
Executable file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Create nginx configuration for reverse proxy to Node.js app
|
||||||
|
cat > /etc/nginx/conf.d/default.conf << EOF
|
||||||
|
upstream nodejs_backend {
|
||||||
|
server 127.0.0.1:3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name $domain $serveralias;
|
||||||
|
|
||||||
|
# Redirect HTTP to HTTPS
|
||||||
|
return 301 https://\$server_name\$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name $domain $serveralias;
|
||||||
|
|
||||||
|
ssl_certificate /etc/pki/tls/certs/localhost.crt;
|
||||||
|
ssl_certificate_key /etc/pki/tls/private/localhost.key;
|
||||||
|
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
|
||||||
|
|
||||||
|
access_log /home/$user/logs/nginx/access.log;
|
||||||
|
error_log /home/$user/logs/nginx/error.log;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://nodejs_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade \$http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host \$host;
|
||||||
|
proxy_set_header X-Real-IP \$remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||||
|
proxy_cache_bypass \$http_upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ping {
|
||||||
|
proxy_pass http://nodejs_backend/ping;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static files
|
||||||
|
location /static/ {
|
||||||
|
alias /home/$user/app/public/;
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
68
scripts/entrypoint.sh
Executable file
68
scripts/entrypoint.sh
Executable file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ -z "$NODEVER" ]; then
|
||||||
|
NODEVER="20";
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$environment" ]; then
|
||||||
|
environment="PROD"
|
||||||
|
fi
|
||||||
|
|
||||||
|
adduser -u $uid $user
|
||||||
|
|
||||||
|
mkdir -p /home/$user/app
|
||||||
|
mkdir -p /home/$user/logs/{nginx,nodejs}
|
||||||
|
|
||||||
|
# Link log directories
|
||||||
|
rm -rf /var/log/nginx
|
||||||
|
ln -s /home/$user/logs/nginx /var/log/nginx
|
||||||
|
ln -s /home/$user/logs/nodejs /var/log/nodejs
|
||||||
|
|
||||||
|
# Configure nginx for reverse proxy
|
||||||
|
/scripts/create-nginx-config.sh
|
||||||
|
|
||||||
|
# Set ownership and permissions
|
||||||
|
chown -R $user:$user /home/$user
|
||||||
|
chmod -R 755 /home/$user
|
||||||
|
|
||||||
|
# Start nginx
|
||||||
|
nginx
|
||||||
|
|
||||||
|
if [[ $environment == 'DEV' ]]; then
|
||||||
|
echo "Starting Dev Deployment"
|
||||||
|
mkdir -p /home/$user/_backups
|
||||||
|
|
||||||
|
# Ensure microdnf is available for installing additional packages in DEV mode
|
||||||
|
if ! command -v microdnf &> /dev/null; then
|
||||||
|
echo "microdnf not found, installing with dnf..."
|
||||||
|
dnf install -y microdnf && dnf clean all
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install Memcached for session storage in DEV mode with memory limit
|
||||||
|
microdnf install -y memcached
|
||||||
|
# Start memcached with 32MB memory limit
|
||||||
|
nohup memcached -d -u $user -p 11211 -m 32
|
||||||
|
|
||||||
|
# Set up automatic backups
|
||||||
|
echo "*/30 * * * * root /scripts/backup.sh $user" >> /etc/crontab
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start cron for log rotation and backups
|
||||||
|
/usr/sbin/crond
|
||||||
|
|
||||||
|
# If there's an app in the user directory, start it with PM2
|
||||||
|
if [ -f /home/$user/app/package.json ]; then
|
||||||
|
cd /home/$user/app
|
||||||
|
su -c "npm install" $user
|
||||||
|
su -c "pm2 start ecosystem.config.js" $user
|
||||||
|
else
|
||||||
|
# Start default app
|
||||||
|
cd /var/www/html
|
||||||
|
npm install
|
||||||
|
su -c "pm2 start ecosystem.config.js" $user
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Follow logs
|
||||||
|
tail -f /home/$user/logs/nginx/* /home/$user/logs/nodejs/*
|
||||||
|
|
||||||
|
exit 0
|
6
scripts/install-node18.sh
Executable file
6
scripts/install-node18.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
curl -fsSL https://rpm.nodesource.com/setup_18.x | bash -
|
||||||
|
dnf install -y nodejs
|
||||||
|
npm install -g npm@latest
|
||||||
|
node --version
|
||||||
|
npm --version
|
6
scripts/install-node20.sh
Executable file
6
scripts/install-node20.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
|
||||||
|
dnf install -y nodejs
|
||||||
|
npm install -g npm@latest
|
||||||
|
node --version
|
||||||
|
npm --version
|
6
scripts/install-node22.sh
Executable file
6
scripts/install-node22.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
curl -fsSL https://rpm.nodesource.com/setup_22.x | bash -
|
||||||
|
dnf install -y nodejs
|
||||||
|
npm install -g npm@latest
|
||||||
|
node --version
|
||||||
|
npm --version
|
18
scripts/log-rotate.sh
Executable file
18
scripts/log-rotate.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Log rotation script for nginx and Node.js logs
|
||||||
|
LOG_DIR="/home/*/logs"
|
||||||
|
|
||||||
|
# Compress logs older than 3 days
|
||||||
|
find $LOG_DIR -name "*.log" -type f -mtime +3 -exec gzip {} \;
|
||||||
|
|
||||||
|
# Delete compressed logs older than 7 days
|
||||||
|
find $LOG_DIR -name "*.gz" -type f -mtime +7 -delete
|
||||||
|
|
||||||
|
# Rotate nginx logs
|
||||||
|
if [ -f /var/run/nginx.pid ]; then
|
||||||
|
kill -USR1 $(cat /var/run/nginx.pid)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rotate PM2 logs
|
||||||
|
pm2 flush
|
58
scripts/memory-info.sh
Executable file
58
scripts/memory-info.sh
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "=== Cloud Node Container Memory Usage ==="
|
||||||
|
echo "Date: $(date)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== System Memory ==="
|
||||||
|
free -h
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Process Memory Usage ==="
|
||||||
|
echo "Process PID VSZ RSS %MEM"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
# Nginx
|
||||||
|
nginx_pid=$(pgrep nginx | head -1)
|
||||||
|
if [ ! -z "$nginx_pid" ]; then
|
||||||
|
ps -o pid,vsz,rss,%mem,comm -p $nginx_pid | tail -n +2 | while read pid vsz rss mem comm; do
|
||||||
|
printf "%-25s %5s %6s %6s %5s\n" "$comm" "$pid" "${vsz}K" "${rss}K" "$mem%"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Node.js/PM2
|
||||||
|
pm2_pids=$(pgrep -f "PM2\|node")
|
||||||
|
if [ ! -z "$pm2_pids" ]; then
|
||||||
|
echo "$pm2_pids" | while read pid; do
|
||||||
|
ps -o pid,vsz,rss,%mem,comm -p $pid 2>/dev/null | tail -n +2 | while read pid vsz rss mem comm; do
|
||||||
|
printf "%-25s %5s %6s %6s %5s\n" "$comm" "$pid" "${vsz}K" "${rss}K" "$mem%"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Memcached
|
||||||
|
memcached_pid=$(pgrep memcached)
|
||||||
|
if [ ! -z "$memcached_pid" ]; then
|
||||||
|
ps -o pid,vsz,rss,%mem,comm -p $memcached_pid | tail -n +2 | while read pid vsz rss mem comm; do
|
||||||
|
printf "%-25s %5s %6s %6s %5s\n" "$comm" "$pid" "${vsz}K" "${rss}K" "$mem%"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Container Resource Limits ==="
|
||||||
|
if [ -f /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then
|
||||||
|
limit=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)
|
||||||
|
if [ $limit -lt 9223372036854775807 ]; then
|
||||||
|
echo "Memory Limit: $((limit / 1024 / 1024))MB"
|
||||||
|
else
|
||||||
|
echo "Memory Limit: No limit set"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== PM2 Process Info ==="
|
||||||
|
if command -v pm2 &> /dev/null; then
|
||||||
|
pm2 list 2>/dev/null | grep -E "(App name|online|stopped|errored)"
|
||||||
|
echo ""
|
||||||
|
pm2 show 0 2>/dev/null | grep -E "(memory|cpu|uptime)" || echo "No PM2 processes running"
|
||||||
|
fi
|
Reference in New Issue
Block a user