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 10 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
NODEVERor-aflag) - 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
/pingendpoint 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
./local-dev.sh -n local-dev
Flags:
-nName of the container (required)-pHTTP port (default: 80)-sHTTPS port (default: 443)-rRoot path for files (default: current directory)-aNode.js version (default: 20; options: 18, 20, 22)-vEnable verbose mode-hShow 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 containerinstance_stop– Stop the containerinstance_logs– View container logsinstance_shell– Access container shell
Manual Docker Usage
You can also run the container manually:
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 repo.anhonesthost.net/cloud-hosting-platform/cnoc:latest
Accessing the Container
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.jsorindex.js)
Optional Files:
public/folder for static files (HTML, CSS, images)ecosystem.config.jsfor advanced PM2 configuration (auto-generated if not provided)
Step 2: What Users Need to Do
If you're not familiar with Node.js, here's what you need to know:
- Create a
package.jsonfile - This tells Node.js about your app:
{
"name": "my-website",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
- Create your main server file - This runs your application:
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}`);
});
-
Put your files in the right place:
- Copy your
package.jsonandserver.jstouser/app/ - Put HTML, CSS, images in
user/app/public/
- Copy your
-
Start the container:
./local-dev.sh -n my-app
That's it! The container will:
- Install your dependencies automatically
- Generate PM2 configuration from your package.json
- Start your application with PM2
- Handle SSL and reverse proxy
- Provide health monitoring
Important package.json fields:
- name: Used as the PM2 process name (defaults to 'node-app')
- main: Entry point file (defaults to 'index.js')
- scripts.start: Alternative way to specify entry point (e.g., "node server.js")
The container automatically generates an ecosystem.config.js file from your package.json if you don't provide one.
PM2 Configuration Notes:
- The auto-generated PM2 config uses fork mode (not cluster mode) for simplicity and compatibility
wait_readyis set tofalseby default - your app doesn't need to send any special signals to PM2- PM2 runs as the specified user (not root) for security - ensure your
uidandusermatch your host system - If you need advanced PM2 features, you can provide your own
ecosystem.config.jsfile
Advanced Usage: If you want to use PM2's wait_ready feature for health checking:
- Create your own
ecosystem.config.jswithwait_ready: true - Add
process.send('ready')in your app when it's ready to accept connections:app.listen(port, () => { console.log(`Server running on port ${port}`); if (process.send) { process.send('ready'); } });
WebSocket Support (socket.io, ws)
WebSockets work out of the box — no configuration needed on your side. Nginx proxies
upgrade requests through to your app on port 3000, and the auto-generated PM2 config
runs a single fork-mode process, so socket.io's session affinity requirement is
satisfied automatically. A stock io.attach(server) / new WebSocketServer({ server })
setup just works over wss://your-domain/.
Things to know before you scale up:
- Don't switch to PM2 cluster mode. If you supply your own
ecosystem.config.jswithexec_mode: 'cluster'andinstancesgreater than 1, socket.io's HTTP long-polling handshake will bounce between workers and clients will seeSession ID unknownerrors. To run multiple instances you need both sticky sessions and a shared adapter (@socket.io/cluster-adapterwith@socket.io/sticky) — the container does not set these up for you. - Concurrency ceiling is roughly 250 simultaneous connections. Nginx runs one
worker with
worker_connections 512, and each proxied client consumes two slots (browser side plus upstream side). Fine for typical traffic; a limit to plan around for a chat or presence-heavy app. - Keep your heartbeat under 600 seconds. Nginx closes idle proxied connections
after 600s with no data. Socket.io's default
pingIntervalof 25s is well inside this. Only relevant if you deliberately raise it. - Expect reconnects on memory restarts. PM2 restarts the app at 256MB
(
max_memory_restart), which drops every open socket. Socket.io clients reconnect automatically, but connection state held only in process memory is lost. WebSocket buffers add up — if you hold thousands of connections, watch for restart loops inlogs/nodejs/.
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:
- Runs
npm installto install dependencies - Starts your app with PM2 process manager
- Sets up Nginx reverse proxy with SSL
- Configures logging and health checks
- Starts Memcached for session storage (in DEV mode)
Troubleshooting for New Users
- App won't start? Check that your
package.jsonhas a valid "start" script - Can't access your site? Make sure your app listens on port 3000 and has a
/pingendpoint - Dependencies missing? List all required packages in your
package.jsondependencies section - Static files not loading? Put them in a
public/folder and useapp.use(express.static('public'))
Features
- Multiple Node.js Versions: 18, 20, 22 (set with
NODEVERenvironment variable) - Process Management: PM2 for production-grade Node.js application management
- Reverse Proxy: Nginx handles SSL termination and proxies requests to Node.js
- WebSockets: socket.io and
wssupported out of the box (see WebSocket Support) - 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,/infoendpoints for monitoring - Static Files: Nginx serves static files from
/home/$user/app/public/
Environment Variables
Required:
uid– User ID for file permissions (must match your host user ID)user– Username for file permissions (must match your host username)domain– Primary domain for configuration
Optional:
environment– Set toDEVto start Redis and enable development featuresserveralias– Comma-separated list of alternative hostnamesNODEVER– 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_startordocker start {container-name} - To stop:
./instance_stopordocker stop {container-name} - To view logs:
./instance_logsordocker logs -f {container-name} - To access shell:
./instance_shellordocker 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.jsonhas a valid start script - Check
/home/$user/logs/nodejs/error.logfor application errors - The health check endpoint
/pingshould return a 200 status when the application is running properly - Memory issues: Run
/scripts/memory-info.shinside container to check memory usage - Process monitoring: Use
pm2 monitto watch application performance
Common Issues and Solutions
502 Bad Gateway errors:
- Check if your app is running on port 3000 (or the PORT environment variable)
- Look at nginx error logs:
/home/$user/logs/nginx/error.log - Verify PM2 status:
docker exec <container> su - <user> -c "pm2 status"
Permission errors:
- Ensure
uidanduserenvironment variables match your host system - Use
$(id -u)and$(whoami)in your docker run command - Files should be owned by your user, not root
App not starting:
- Check PM2 logs:
/home/$user/logs/nodejs/error.log - Verify your package.json has correct
mainorscripts.startfield - Ensure all dependencies are listed in package.json