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