Skip to content

Deploying E-Label

Introduction

E-Label is a modern, open-source digital label and QR code management system designed for inventory management, product labeling, asset tracking, and digital signage. Built with a focus on simplicity and efficiency, E-Label enables organizations to create, manage, and track physical items through customizable digital labels and QR codes. Whether you’re managing warehouse inventory, tracking assets, organizing retail products, or creating interactive labels for museums and exhibitions, E-Label provides the tools you need.

The platform combines label design capabilities with robust tracking features, allowing users to generate professional labels with embedded QR codes, barcodes, and dynamic content. E-Label supports batch generation, template management, and real-time updates, making it ideal for businesses that need scalable labeling solutions without the complexity of enterprise systems.

Key Features:

  • Custom Label Design: Create professional labels with drag-and-drop design tools, custom templates, and branding options
  • QR Code Generation: Generate dynamic QR codes with tracking capabilities, custom URLs, and embedded data
  • Barcode Support: Multiple barcode formats including Code 128, EAN-13, UPC, QR codes, and Data Matrix
  • Template Management: Save and reuse label templates for consistent branding across products and locations
  • Batch Processing: Generate hundreds or thousands of labels at once with CSV import and bulk operations
  • Asset Tracking: Track physical items through the entire lifecycle with scan history and location updates
  • Multi-Format Export: Export labels to PDF, PNG, SVG for printing or digital display
  • API Integration: RESTful API for programmatic label generation and management
  • Database Storage: PostgreSQL backend for reliable data persistence and complex queries
  • Responsive Design: Mobile-friendly interface for scanning and updating labels in the field
  • User Management: Role-based access control with team collaboration features
  • Print Optimization: Pre-configured settings for common label printers and paper sizes

This comprehensive guide walks you through deploying E-Label on Klutch.sh using Docker, configuring PostgreSQL for data storage, setting up persistent volumes for uploaded assets, and implementing production best practices for reliable label management.

Why Deploy E-Label on Klutch.sh

Deploying E-Label on Klutch.sh provides several advantages for digital label management:

  • Automated Deployment: Klutch.sh automatically detects your Dockerfile and builds E-Label with zero manual configuration
  • PostgreSQL Integration: Easy connection to managed PostgreSQL databases for storing labels, templates, and tracking data
  • Persistent Storage: Attach persistent volumes for uploaded images, generated labels, and template assets
  • Scalable Infrastructure: Handle thousands of concurrent label generation requests with automatic resource scaling
  • Secure Environment Variables: Store database credentials and API keys securely without committing them to version control
  • GitHub Integration: Automatic redeployment when you push updates to your E-Label configuration or custom themes
  • Custom Domains: Use your own branded domain for the label management interface
  • HTTPS by Default: Automatic SSL/TLS certificates for secure label access and API communication
  • High Availability: Deploy with built-in redundancy for critical labeling operations
  • Cost Efficiency: Pay only for the resources you use, ideal for businesses with variable labeling needs

Prerequisites

Before deploying E-Label on Klutch.sh, ensure you have:

  • A Klutch.sh account
  • A GitHub account for repository hosting
  • Docker installed locally for testing (optional but recommended)
  • Basic understanding of label printing, QR codes, and inventory management
  • Familiarity with Docker and web applications
  • A PostgreSQL database for storing label data (can be deployed on Klutch.sh)
  • (Optional) Label printer compatible with standard formats (PDF, PNG)
  • (Optional) Mobile device with QR code scanning capability for testing

Understanding E-Label Architecture

E-Label uses a modular architecture designed for scalability and ease of deployment:

Core Components

Web Application: Node.js/Express backend serving the label management interface and API

  • Built with modern JavaScript frameworks (React/Vue for frontend)
  • RESTful API for label operations and tracking
  • Template engine for dynamic label generation
  • Image processing for logo uploads and custom graphics
  • PDF generation engine for printable labels
  • QR code and barcode generation libraries

Database Layer: PostgreSQL for structured data storage

  • Label definitions and metadata
  • Template configurations
  • User accounts and permissions
  • Scan history and tracking logs
  • Asset information and location data

File Storage: Persistent volume for uploaded assets

  • Custom logos and images
  • Generated label PDFs and images
  • Template exports and backups
  • Temporary files for batch processing

Data Flow

  1. User creates label template or uses existing template
  2. Label data entered manually or imported from CSV
  3. E-Label generates preview with QR code/barcode
  4. User approves and triggers batch generation
  5. Labels rendered to PDF or image formats
  6. Files stored in persistent volume
  7. Download links provided for printing or digital use
  8. QR code scans tracked in database for analytics

Port and Protocol Requirements

  • HTTP/HTTPS: Port 3000 (internal) for web interface and API
  • Database: PostgreSQL connection on port 5432 (or 8000 via Klutch.sh)

Preparing Your Repository

To deploy E-Label on Klutch.sh, create a GitHub repository with the necessary configuration files.

Step 1: Create Repository Structure

Create a new directory for your E-Label deployment:

Terminal window
mkdir e-label-klutch
cd e-label-klutch
git init

Create the following directory structure:

e-label-klutch/
├── Dockerfile
├── docker-compose.yml # For local testing
├── .dockerignore
├── .env.example
├── config/
│ └── settings.js
├── public/
│ └── uploads/ # Will be mounted as volume
├── scripts/
│ └── init-db.sql
└── README.md

Step 2: Create the Dockerfile

Create a production-ready Dockerfile:

# Build stage - compile frontend assets
FROM node:20-alpine AS build
WORKDIR /app
# Install build dependencies
RUN apk add --no-cache python3 make g++ cairo-dev jpeg-dev pango-dev giflib-dev
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application code
COPY . .
# Build frontend if needed
RUN npm run build 2>/dev/null || echo "No build step required"
# Production stage - minimal runtime
FROM node:20-alpine
WORKDIR /app
# Install runtime dependencies for PDF and image generation
RUN apk add --no-cache \
cairo \
jpeg \
pango \
giflib \
fontconfig \
ttf-dejavu \
curl
# Copy from build stage
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app .
# Create directories for uploads and generated files
RUN mkdir -p /app/public/uploads \
&& mkdir -p /app/public/generated \
&& mkdir -p /app/data \
&& chown -R node:node /app
# Switch to non-root user
USER node
# Expose application port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Start application
CMD ["node", "server.js"]

Dockerfile Explanation:

  • Multi-stage build: Separates build dependencies from runtime for smaller image
  • Alpine base: Lightweight Linux distribution reduces image size
  • Cairo/Pango: Required for PDF generation and custom fonts
  • Non-root user: Security best practice for production deployments
  • Health check: Monitors application availability
  • Port 3000: Default E-Label application port

Step 3: Create Application Entry Point

Create server.js if not already present in the E-Label codebase:

const express = require('express');
const path = require('path');
const { Pool } = require('pg');
const QRCode = require('qrcode');
const PDFDocument = require('pdfkit');
const bwipjs = require('bwip-js');
const app = express();
const PORT = process.env.PORT || 3000;
// Database configuration
const pool = new Pool({
host: process.env.DATABASE_HOST || 'localhost',
port: process.env.DATABASE_PORT || 5432,
database: process.env.DATABASE_NAME || 'elabel',
user: process.env.DATABASE_USER || 'elabel',
password: process.env.DATABASE_PASSWORD || '',
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/uploads', express.static(path.join(__dirname, 'public/uploads')));
app.use('/generated', express.static(path.join(__dirname, 'public/generated')));
app.use(express.static('public'));
// Health check endpoint
app.get('/health', async (req, res) => {
try {
await pool.query('SELECT 1');
res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });
} catch (error) {
res.status(503).json({ status: 'unhealthy', error: error.message });
}
});
// Generate QR code endpoint
app.post('/api/labels/qr', async (req, res) => {
try {
const { data, format = 'png', size = 300 } = req.body;
if (!data) {
return res.status(400).json({ error: 'Data is required' });
}
const qrCode = await QRCode.toDataURL(data, {
width: size,
margin: 2,
errorCorrectionLevel: 'M'
});
res.json({ qrCode, format: 'data-url' });
} catch (error) {
console.error('QR generation error:', error);
res.status(500).json({ error: 'Failed to generate QR code' });
}
});
// Generate barcode endpoint
app.post('/api/labels/barcode', async (req, res) => {
try {
const { data, type = 'code128', width = 2, height = 50 } = req.body;
if (!data) {
return res.status(400).json({ error: 'Data is required' });
}
const png = await bwipjs.toBuffer({
bcid: type,
text: data,
scale: width,
height: height,
includetext: true,
});
res.set('Content-Type', 'image/png');
res.send(png);
} catch (error) {
console.error('Barcode generation error:', error);
res.status(500).json({ error: 'Failed to generate barcode' });
}
});
// Create label endpoint
app.post('/api/labels', async (req, res) => {
const client = await pool.connect();
try {
const { name, content, template_id, metadata } = req.body;
const result = await client.query(
'INSERT INTO labels (name, content, template_id, metadata, created_at) VALUES ($1, $2, $3, $4, NOW()) RETURNING *',
[name, content, template_id, metadata]
);
res.status(201).json(result.rows[0]);
} catch (error) {
console.error('Label creation error:', error);
res.status(500).json({ error: 'Failed to create label' });
} finally {
client.release();
}
});
// List labels endpoint
app.get('/api/labels', async (req, res) => {
try {
const { limit = 50, offset = 0 } = req.query;
const result = await pool.query(
'SELECT * FROM labels ORDER BY created_at DESC LIMIT $1 OFFSET $2',
[limit, offset]
);
res.json({ labels: result.rows, count: result.rowCount });
} catch (error) {
console.error('Label fetch error:', error);
res.status(500).json({ error: 'Failed to fetch labels' });
}
});
// Generate PDF label endpoint
app.get('/api/labels/:id/pdf', async (req, res) => {
const client = await pool.connect();
try {
const { id } = req.params;
const result = await client.query('SELECT * FROM labels WHERE id = $1', [id]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Label not found' });
}
const label = result.rows[0];
const doc = new PDFDocument({ size: 'A4', margin: 50 });
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename=label-${id}.pdf`);
doc.pipe(res);
// Add label content
doc.fontSize(16).text(label.name, { align: 'center' });
doc.moveDown();
doc.fontSize(12).text(label.content);
// Generate and add QR code
if (label.metadata && label.metadata.qr_data) {
const qrCodeDataURL = await QRCode.toDataURL(label.metadata.qr_data, { width: 200 });
const qrBuffer = Buffer.from(qrCodeDataURL.split(',')[1], 'base64');
doc.image(qrBuffer, { fit: [200, 200], align: 'center' });
}
doc.end();
} catch (error) {
console.error('PDF generation error:', error);
res.status(500).json({ error: 'Failed to generate PDF' });
} finally {
client.release();
}
});
// Track scan endpoint
app.post('/api/scans', async (req, res) => {
const client = await pool.connect();
try {
const { label_id, location, metadata } = req.body;
const result = await client.query(
'INSERT INTO scans (label_id, location, metadata, scanned_at) VALUES ($1, $2, $3, NOW()) RETURNING *',
[label_id, location, metadata]
);
res.status(201).json(result.rows[0]);
} catch (error) {
console.error('Scan tracking error:', error);
res.status(500).json({ error: 'Failed to track scan' });
} finally {
client.release();
}
});
// Initialize database on startup
async function initializeDatabase() {
const client = await pool.connect();
try {
// Create tables if they don't exist
await client.query(`
CREATE TABLE IF NOT EXISTS labels (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
content TEXT,
template_id INTEGER,
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS templates (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
config JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS scans (
id SERIAL PRIMARY KEY,
label_id INTEGER REFERENCES labels(id),
location VARCHAR(255),
metadata JSONB,
scanned_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_labels_created_at ON labels(created_at);
CREATE INDEX IF NOT EXISTS idx_scans_label_id ON scans(label_id);
CREATE INDEX IF NOT EXISTS idx_scans_scanned_at ON scans(scanned_at);
`);
console.log('Database initialized successfully');
} catch (error) {
console.error('Database initialization error:', error);
throw error;
} finally {
client.release();
}
}
// Start server
(async () => {
try {
await initializeDatabase();
app.listen(PORT, '0.0.0.0', () => {
console.log(`E-Label server running on port ${PORT}`);
console.log(`Health check: http://localhost:${PORT}/health`);
});
} catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
})();
// Graceful shutdown
process.on('SIGTERM', async () => {
console.log('SIGTERM received, shutting down gracefully');
await pool.end();
process.exit(0);
});

Step 4: Create Package Configuration

Create package.json:

{
"name": "e-label",
"version": "1.0.0",
"description": "Digital label and QR code management system",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"build": "echo 'No build step required'"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3",
"qrcode": "^1.5.3",
"pdfkit": "^0.13.0",
"bwip-js": "^4.1.1",
"dotenv": "^16.3.1",
"multer": "^1.4.5-lts.1",
"sharp": "^0.33.0"
},
"devDependencies": {
"nodemon": "^3.0.2"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
}
}

Step 5: Create Environment Configuration

Create .env.example:

# E-Label Environment Configuration
# Application
NODE_ENV=production
PORT=3000
APP_NAME=E-Label
BASE_URL=https://example-app.klutch.sh
# Database Configuration
DATABASE_HOST=your-postgres-app.klutch.sh
DATABASE_PORT=8000
DATABASE_NAME=elabel
DATABASE_USER=elabel
DATABASE_PASSWORD=your-secure-password
# File Storage
UPLOAD_DIR=/app/public/uploads
GENERATED_DIR=/app/public/generated
MAX_FILE_SIZE=10485760
# Label Generation
DEFAULT_LABEL_WIDTH=400
DEFAULT_LABEL_HEIGHT=300
DEFAULT_QR_SIZE=200
SUPPORTED_BARCODES=code128,ean13,upc,qrcode,datamatrix
# API Configuration
API_KEY=your-api-key-here
API_RATE_LIMIT=100
# Session (optional)
SESSION_SECRET=your-session-secret
# Email (optional for notifications)
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
SMTP_FROM=noreply@example.com

Step 6: Create Docker Ignore File

Create .dockerignore:

node_modules
npm-debug.log
.env
.env.local
.git
.gitignore
.DS_Store
*.md
public/uploads/*
public/generated/*
docker-compose.yml

Step 7: Create Docker Compose for Local Testing

Create docker-compose.yml:

version: '3.8'
services:
postgres:
image: postgres:16-alpine
container_name: elabel-postgres
environment:
POSTGRES_DB: elabel
POSTGRES_USER: elabel
POSTGRES_PASSWORD: elabel_password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U elabel"]
interval: 10s
timeout: 5s
retries: 5
elabel:
build: .
container_name: elabel-app
ports:
- "3000:3000"
environment:
NODE_ENV: development
PORT: 3000
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_NAME: elabel
DATABASE_USER: elabel
DATABASE_PASSWORD: elabel_password
BASE_URL: http://localhost:3000
volumes:
- ./public/uploads:/app/public/uploads
- ./public/generated:/app/public/generated
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
volumes:
postgres_data:

Step 8: Create Database Initialization Script

Create scripts/init-db.sql:

-- E-Label Database Schema
-- Labels table
CREATE TABLE IF NOT EXISTS labels (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
content TEXT,
template_id INTEGER,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Templates table
CREATE TABLE IF NOT EXISTS templates (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
config JSONB NOT NULL,
is_default BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Scans table for tracking
CREATE TABLE IF NOT EXISTS scans (
id SERIAL PRIMARY KEY,
label_id INTEGER REFERENCES labels(id) ON DELETE CASCADE,
location VARCHAR(255),
device_info VARCHAR(255),
ip_address INET,
metadata JSONB DEFAULT '{}',
scanned_at TIMESTAMP DEFAULT NOW()
);
-- Users table (optional for authentication)
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(255),
role VARCHAR(50) DEFAULT 'user',
created_at TIMESTAMP DEFAULT NOW(),
last_login TIMESTAMP
);
-- Create indexes
CREATE INDEX IF NOT EXISTS idx_labels_created_at ON labels(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_labels_template_id ON labels(template_id);
CREATE INDEX IF NOT EXISTS idx_scans_label_id ON scans(label_id);
CREATE INDEX IF NOT EXISTS idx_scans_scanned_at ON scans(scanned_at DESC);
CREATE INDEX IF NOT EXISTS idx_templates_is_default ON templates(is_default);
-- Insert default template
INSERT INTO templates (name, description, config, is_default)
VALUES (
'Standard Product Label',
'Default template for product labels with QR code',
'{
"width": 400,
"height": 300,
"fontSize": 14,
"fontFamily": "Arial",
"includeQR": true,
"qrSize": 150,
"backgroundColor": "#FFFFFF",
"textColor": "#000000"
}',
true
) ON CONFLICT DO NOTHING;
-- Create function to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Create triggers
CREATE TRIGGER update_labels_updated_at BEFORE UPDATE ON labels
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_templates_updated_at BEFORE UPDATE ON templates
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

Step 9: Create README

Create README.md:

# E-Label on Klutch.sh
Digital label and QR code management system for inventory, asset tracking, and product labeling.
## Features
- Custom label design with templates
- QR code and barcode generation
- Batch label processing
- Asset tracking with scan history
- Multi-format export (PDF, PNG, SVG)
- PostgreSQL backend
- RESTful API
- Mobile-friendly scanning
## Local Development
Test locally with Docker Compose:
```bash
docker-compose up -d

Access E-Label at: http://localhost:3000

Production Deployment on Klutch.sh

Required Environment Variables

Set these in the Klutch.sh dashboard:

Database Configuration:

  • DATABASE_HOST: PostgreSQL host (e.g., your-postgres-app.klutch.sh)
  • DATABASE_PORT: PostgreSQL port (8000 for Klutch.sh deployments)
  • DATABASE_NAME: Database name (e.g., elabel)
  • DATABASE_USER: Database user
  • DATABASE_PASSWORD: Database password

Application Settings:

  • BASE_URL: Your app URL (e.g., https://elabel.klutch.sh)
  • APP_NAME: Application name for branding
  • API_KEY: API key for programmatic access

Persistent Volumes

Attach a persistent volume for uploaded assets:

  • Mount Path: /app/public/uploads
  • Recommended Size: 10-50GB depending on usage

Traffic Configuration

  • Traffic Type: HTTP
  • Internal Port: 3000

API Usage

Generate QR Code

Terminal window
curl -X POST https://example-app.klutch.sh/api/labels/qr \
-H "Content-Type: application/json" \
-d '{"data": "https://example.com/product/12345", "size": 300}'

Create Label

Terminal window
curl -X POST https://example-app.klutch.sh/api/labels \
-H "Content-Type: application/json" \
-d '{
"name": "Product XYZ",
"content": "Description of product",
"metadata": {"sku": "12345", "qr_data": "https://example.com/product/12345"}
}'

Track Scan

Terminal window
curl -X POST https://example-app.klutch.sh/api/scans \
-H "Content-Type: application/json" \
-d '{
"label_id": 1,
"location": "Warehouse A",
"metadata": {"device": "Mobile Scanner"}
}'

License

MIT License

### Step 10: Initialize Git and Push to GitHub
```bash
# Add all files
git add .
# Commit
git commit -m "Initial E-Label deployment configuration"
# Add GitHub remote (replace with your repository URL)
git remote add origin https://github.com/yourusername/e-label-klutch.git
# Push to GitHub
git branch -M main
git push -u origin main

Deploying to Klutch.sh

Now that your repository is prepared, follow these steps to deploy E-Label on Klutch.sh.

Prerequisites: Deploy PostgreSQL Database

E-Label requires PostgreSQL for data storage. Deploy it first:

  1. **Follow PostgreSQL Guide**

    Deploy PostgreSQL using our PostgreSQL deployment guide.

  2. **Create E-Label Database**

    Connect to your PostgreSQL instance and create the database:

    CREATE DATABASE elabel;
    CREATE USER elabel WITH PASSWORD 'your-secure-password';
    GRANT ALL PRIVILEGES ON DATABASE elabel TO elabel;
  3. **Note Connection Details**

    You’ll need these for E-Label configuration:

    • Host: your-postgres-app.klutch.sh
    • Port: 8000
    • Database: elabel
    • User: elabel
    • Password: Your chosen password

Deployment Steps

  1. **Navigate to Klutch.sh Dashboard**

    Visit klutch.sh/app and log in to your account.

  2. **Create a New Project**

    Click “New Project” and give it a name like “E-Label System” to organize your deployment.

  3. **Create a New App**

    Click “New App” or “Create App” and select GitHub as your source.

  4. **Connect Your Repository**
    • Authenticate with GitHub if not already connected
    • Select your E-Label repository from the list
    • Choose the main branch for deployment
  5. **Configure Application Settings**
    • App Name: Choose a unique name (e.g., elabel-prod)
    • Traffic Type: Select HTTP for web interface access
    • Internal Port: Set to 3000 (E-Label’s default port)
  6. **Set Environment Variables**

    Configure these environment variables in the Klutch.sh dashboard:

    Required - Database Configuration:

    DATABASE_HOST=your-postgres-app.klutch.sh
    DATABASE_PORT=8000
    DATABASE_NAME=elabel
    DATABASE_USER=elabel
    DATABASE_PASSWORD=your-secure-password

    Required - Application Settings:

    NODE_ENV=production
    PORT=3000
    BASE_URL=https://your-app-name.klutch.sh
    APP_NAME=E-Label Production

    Optional - API Configuration:

    API_KEY=your-generated-api-key
    API_RATE_LIMIT=100

    Optional - File Upload Limits:

    MAX_FILE_SIZE=10485760
    UPLOAD_DIR=/app/public/uploads
    GENERATED_DIR=/app/public/generated
  7. **Attach Persistent Volume**

    Critical for storing uploaded images and generated labels:

    • Click “Add Volume” in the Volumes section
    • Mount Path: /app/public/uploads
    • Size: 10GB minimum, 20-50GB recommended for production

    This volume stores:

    • Uploaded logos and images
    • Generated label files
    • Template assets
    • Temporary batch processing files
  8. **Deploy Application**

    Click “Create” or “Deploy” to start the deployment. Klutch.sh will:

    • Automatically detect your Dockerfile
    • Build the Docker image with Node.js and dependencies
    • Install PDF generation libraries (Cairo, Pango)
    • Attach the persistent volume
    • Start your E-Label container
    • Assign a URL for external access

    The first deployment takes 3-5 minutes as dependencies are installed and the database is initialized.

  9. **Verify Deployment**

    Once deployed, your E-Label instance will be available at:

    https://your-app-name.klutch.sh

    Test the deployment:

    Health Check:

    Terminal window
    curl https://your-app-name.klutch.sh/health

    Expected response:

    {
    "status": "healthy",
    "timestamp": "2024-12-17T10:00:00.000Z"
    }

    Database Connection:

    The health check endpoint also verifies database connectivity. If you see "status": "healthy", your PostgreSQL connection is working.

  10. **Initialize Database Schema**

    The application automatically creates tables on first startup. Verify by checking logs or testing the API:

    Terminal window
    curl https://your-app-name.klutch.sh/api/labels

    Expected response:

    {
    "labels": [],
    "count": 0
    }

Getting Started with E-Label

After deploying E-Label on Klutch.sh, follow these steps to create your first labels.

Creating Your First Label

Use the API to create a simple label with a QR code:

Terminal window
curl -X POST https://your-app-name.klutch.sh/api/labels \
-H "Content-Type: application/json" \
-d '{
"name": "Warehouse Item A001",
"content": "Premium Quality Product - Serial: WH-A001",
"metadata": {
"sku": "A001",
"location": "Warehouse Section A",
"qr_data": "https://your-company.com/items/A001"
}
}'

Response:

{
"id": 1,
"name": "Warehouse Item A001",
"content": "Premium Quality Product - Serial: WH-A001",
"template_id": null,
"metadata": {
"sku": "A001",
"location": "Warehouse Section A",
"qr_data": "https://your-company.com/items/A001"
},
"created_at": "2024-12-17T10:00:00.000Z",
"updated_at": "2024-12-17T10:00:00.000Z"
}

Generating a QR Code

Generate a standalone QR code:

Terminal window
curl -X POST https://your-app-name.klutch.sh/api/labels/qr \
-H "Content-Type: application/json" \
-d '{
"data": "https://your-company.com/products/12345",
"size": 300,
"format": "png"
}'

Response includes data URL that can be embedded in HTML:

{
"qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"format": "data-url"
}

Generating a Barcode

Create a Code 128 barcode:

Terminal window
curl -X POST https://your-app-name.klutch.sh/api/labels/barcode \
-H "Content-Type: application/json" \
-d '{
"data": "123456789012",
"type": "code128",
"width": 2,
"height": 50
}' --output barcode.png

Supported barcode types:

  • code128: Code 128 (alphanumeric)
  • ean13: EAN-13 (13 digits)
  • upca: UPC-A (12 digits)
  • qrcode: QR Code
  • datamatrix: Data Matrix

Downloading Label as PDF

Generate a printable PDF for a label:

Terminal window
curl https://your-app-name.klutch.sh/api/labels/1/pdf --output label.pdf

This creates a formatted PDF with:

  • Label name and content
  • Embedded QR code
  • Professional layout for printing

Tracking Label Scans

Record when a label is scanned (useful for inventory tracking):

Terminal window
curl -X POST https://your-app-name.klutch.sh/api/scans \
-H "Content-Type: application/json" \
-d '{
"label_id": 1,
"location": "Warehouse Loading Dock",
"metadata": {
"scanner": "Mobile App",
"user": "John Doe",
"notes": "Outbound shipment scan"
}
}'

Response:

{
"id": 1,
"label_id": 1,
"location": "Warehouse Loading Dock",
"metadata": {
"scanner": "Mobile App",
"user": "John Doe",
"notes": "Outbound shipment scan"
},
"scanned_at": "2024-12-17T14:30:00.000Z"
}

Batch Label Creation

For creating multiple labels at once, you can script the API:

#!/bin/bash
# labels.csv format: name,content,sku,location
while IFS=',' read -r name content sku location; do
curl -X POST https://your-app-name.klutch.sh/api/labels \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$name\",
\"content\": \"$content\",
\"metadata\": {
\"sku\": \"$sku\",
\"location\": \"$location\",
\"qr_data\": \"https://your-company.com/items/$sku\"
}
}"
sleep 0.5 # Rate limiting
done < labels.csv

API Integration Examples

Node.js Client

const axios = require('axios');
class ELabelClient {
constructor(baseURL, apiKey = null) {
this.client = axios.create({
baseURL,
headers: apiKey ? { 'X-API-Key': apiKey } : {}
});
}
async createLabel(name, content, metadata = {}) {
const response = await this.client.post('/api/labels', {
name,
content,
metadata
});
return response.data;
}
async generateQRCode(data, size = 300) {
const response = await this.client.post('/api/labels/qr', {
data,
size
});
return response.data.qrCode;
}
async downloadLabelPDF(labelId) {
const response = await this.client.get(`/api/labels/${labelId}/pdf`, {
responseType: 'arraybuffer'
});
return response.data;
}
async trackScan(labelId, location, metadata = {}) {
const response = await this.client.post('/api/scans', {
label_id: labelId,
location,
metadata
});
return response.data;
}
async getLabels(limit = 50, offset = 0) {
const response = await this.client.get('/api/labels', {
params: { limit, offset }
});
return response.data;
}
}
// Usage
const elabel = new ELabelClient('https://example-app.klutch.sh', 'your-api-key');
(async () => {
// Create a new label
const label = await elabel.createLabel(
'Product ABC',
'High-quality product with extended warranty',
{ sku: 'ABC123', price: 99.99 }
);
console.log('Label created:', label.id);
// Download PDF
const pdf = await elabel.downloadLabelPDF(label.id);
require('fs').writeFileSync('label.pdf', pdf);
console.log('Label PDF saved');
})();

Python Client

import requests
import json
class ELabelClient:
def __init__(self, base_url, api_key=None):
self.base_url = base_url
self.headers = {'Content-Type': 'application/json'}
if api_key:
self.headers['X-API-Key'] = api_key
def create_label(self, name, content, metadata=None):
url = f"{self.base_url}/api/labels"
data = {
'name': name,
'content': content,
'metadata': metadata or {}
}
response = requests.post(url, json=data, headers=self.headers)
response.raise_for_status()
return response.json()
def generate_qr_code(self, data, size=300):
url = f"{self.base_url}/api/labels/qr"
payload = {'data': data, 'size': size}
response = requests.post(url, json=payload, headers=self.headers)
response.raise_for_status()
return response.json()['qrCode']
def download_label_pdf(self, label_id, output_path):
url = f"{self.base_url}/api/labels/{label_id}/pdf"
response = requests.get(url, headers=self.headers)
response.raise_for_status()
with open(output_path, 'wb') as f:
f.write(response.content)
def track_scan(self, label_id, location, metadata=None):
url = f"{self.base_url}/api/scans"
data = {
'label_id': label_id,
'location': location,
'metadata': metadata or {}
}
response = requests.post(url, json=data, headers=self.headers)
response.raise_for_status()
return response.json()
def get_labels(self, limit=50, offset=0):
url = f"{self.base_url}/api/labels"
params = {'limit': limit, 'offset': offset}
response = requests.get(url, params=params, headers=self.headers)
response.raise_for_status()
return response.json()
# Usage
client = ELabelClient('https://example-app.klutch.sh', 'your-api-key')
# Create labels in batch
products = [
{'name': 'Product A', 'sku': 'A001'},
{'name': 'Product B', 'sku': 'B002'},
{'name': 'Product C', 'sku': 'C003'}
]
for product in products:
label = client.create_label(
name=product['name'],
content=f"SKU: {product['sku']}",
metadata={'sku': product['sku']}
)
print(f"Created label {label['id']} for {product['name']}")
# Download PDF
client.download_label_pdf(label['id'], f"label_{product['sku']}.pdf")

PHP Client

<?php
class ELabelClient {
private $baseURL;
private $apiKey;
public function __construct($baseURL, $apiKey = null) {
$this->baseURL = rtrim($baseURL, '/');
$this->apiKey = $apiKey;
}
private function request($method, $endpoint, $data = null) {
$url = $this->baseURL . $endpoint;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
$headers = ['Content-Type: application/json'];
if ($this->apiKey) {
$headers[] = 'X-API-Key: ' . $this->apiKey;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
throw new Exception("API request failed with status $httpCode");
}
return json_decode($response, true);
}
public function createLabel($name, $content, $metadata = []) {
return $this->request('POST', '/api/labels', [
'name' => $name,
'content' => $content,
'metadata' => $metadata
]);
}
public function generateQRCode($data, $size = 300) {
$result = $this->request('POST', '/api/labels/qr', [
'data' => $data,
'size' => $size
]);
return $result['qrCode'];
}
public function trackScan($labelId, $location, $metadata = []) {
return $this->request('POST', '/api/scans', [
'label_id' => $labelId,
'location' => $location,
'metadata' => $metadata
]);
}
public function getLabels($limit = 50, $offset = 0) {
return $this->request('GET', "/api/labels?limit=$limit&offset=$offset");
}
}
// Usage
$client = new ELabelClient('https://example-app.klutch.sh', 'your-api-key');
// Create label
$label = $client->createLabel(
'Warehouse Item',
'Description of item',
['sku' => 'WH001', 'location' => 'Aisle 5']
);
echo "Label created with ID: " . $label['id'] . "\n";
// Track scan
$scan = $client->trackScan(
$label['id'],
'Receiving Dock',
['scanner' => 'Handheld Device', 'user' => 'operator1']
);
echo "Scan tracked at: " . $scan['scanned_at'] . "\n";

Advanced Configuration

Custom Label Templates

Create a custom template via the database:

INSERT INTO templates (name, description, config, is_default)
VALUES (
'Retail Price Label',
'Template for retail shelf labels with price and QR code',
'{
"width": 300,
"height": 200,
"fontSize": 12,
"fontFamily": "Helvetica",
"includeQR": true,
"qrSize": 100,
"includePriceField": true,
"backgroundColor": "#FFFFFF",
"textColor": "#000000",
"borderWidth": 2,
"borderColor": "#333333"
}',
false
);

Environment-Based Configuration

For different environments (development, staging, production):

Development:

NODE_ENV=development
DATABASE_HOST=localhost
DATABASE_PORT=5432
BASE_URL=http://localhost:3000

Staging:

NODE_ENV=staging
DATABASE_HOST=staging-postgres.klutch.sh
DATABASE_PORT=8000
BASE_URL=https://elabel-staging.klutch.sh

Production:

NODE_ENV=production
DATABASE_HOST=production-postgres.klutch.sh
DATABASE_PORT=8000
BASE_URL=https://elabel.klutch.sh
API_RATE_LIMIT=1000

Scaling Configuration

For high-volume label generation:

Increase File Upload Limits:

MAX_FILE_SIZE=52428800

Database Connection Pool:

Modify server.js pool configuration:

const pool = new Pool({
host: process.env.DATABASE_HOST,
port: process.env.DATABASE_PORT,
database: process.env.DATABASE_NAME,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
max: 50, // Increased from 20
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});

Memory Limits:

For large PDF generation, increase Node.js memory:

CMD ["node", "--max-old-space-size=2048", "server.js"]

Production Best Practices

Database Management

Connection Pooling:

  • Use connection pools to avoid exhausting database connections
  • Set max: 20-50 connections based on expected load
  • Monitor connection usage in production

Indexing:

The provided schema includes essential indexes. For high-volume deployments, add:

CREATE INDEX idx_labels_name ON labels(name);
CREATE INDEX idx_scans_location ON scans(location);
CREATE INDEX idx_labels_metadata_sku ON labels((metadata->>'sku'));

Regular Backups:

Terminal window
# Backup E-Label database
pg_dump -h your-postgres-app.klutch.sh -p 8000 -U elabel elabel > elabel_backup.sql
# Restore from backup
psql -h your-postgres-app.klutch.sh -p 8000 -U elabel elabel < elabel_backup.sql

File Storage Management

Cleanup Old Generated Files:

Add a cleanup script to remove old temporary files:

cleanup.js
const fs = require('fs').promises;
const path = require('path');
async function cleanupOldFiles(directory, maxAgeDays = 7) {
const now = Date.now();
const maxAge = maxAgeDays * 24 * 60 * 60 * 1000;
const files = await fs.readdir(directory);
for (const file of files) {
const filePath = path.join(directory, file);
const stats = await fs.stat(filePath);
if (now - stats.mtime.getTime() > maxAge) {
await fs.unlink(filePath);
console.log(`Deleted old file: ${file}`);
}
}
}
// Run cleanup
cleanupOldFiles(process.env.GENERATED_DIR || '/app/public/generated')
.then(() => console.log('Cleanup complete'))
.catch(err => console.error('Cleanup error:', err));

Volume Size Monitoring:

Monitor persistent volume usage:

Terminal window
du -sh /app/public/uploads
du -sh /app/public/generated

Security Hardening

API Key Authentication:

Add API key middleware:

function authenticateAPIKey(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.API_KEY) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
// Apply to protected routes
app.use('/api/labels', authenticateAPIKey);

Rate Limiting:

const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});
app.use('/api/', limiter);

Input Validation:

function validateLabelInput(req, res, next) {
const { name, content } = req.body;
if (!name || name.length > 255) {
return res.status(400).json({ error: 'Invalid name' });
}
if (content && content.length > 10000) {
return res.status(400).json({ error: 'Content too long' });
}
next();
}
app.post('/api/labels', validateLabelInput, async (req, res) => {
// Handle label creation
});

Monitoring and Logging

Structured Logging:

const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console({
format: winston.format.simple()
})
]
});
// Use throughout application
logger.info('Label created', { labelId: result.rows[0].id, user: 'admin' });
logger.error('Database error', { error: err.message });

Health Check Monitoring:

Set up external monitoring to check /health endpoint:

Terminal window
# Example with cron
*/5 * * * * curl -f https://elabel.klutch.sh/health || echo "E-Label health check failed"

Performance Metrics:

Add basic metrics endpoint:

let metrics = {
totalLabels: 0,
totalScans: 0,
totalQRCodes: 0,
uptime: process.uptime()
};
app.get('/api/metrics', async (req, res) => {
try {
const labelCount = await pool.query('SELECT COUNT(*) FROM labels');
const scanCount = await pool.query('SELECT COUNT(*) FROM scans');
res.json({
labels: parseInt(labelCount.rows[0].count),
scans: parseInt(scanCount.rows[0].count),
uptime: process.uptime(),
memory: process.memoryUsage(),
timestamp: new Date().toISOString()
});
} catch (error) {
res.status(500).json({ error: 'Failed to fetch metrics' });
}
});

Troubleshooting

Issue: Application Won’t Start

Symptoms: Container starts but application doesn’t respond

Possible Causes and Solutions:

  1. Database Connection Failed:

Check PostgreSQL connectivity:

Terminal window
# Test from Klutch.sh app logs
# Look for: "Database initialization error" or "ECONNREFUSED"

Solution: Verify environment variables:

DATABASE_HOST=your-postgres-app.klutch.sh
DATABASE_PORT=8000
DATABASE_NAME=elabel
DATABASE_USER=elabel
DATABASE_PASSWORD=correct-password
  1. Port Already in Use:

Ensure PORT=3000 is set correctly and not conflicting.

  1. Missing Dependencies:

Check build logs for npm install errors. Rebuild if needed.

Issue: PDF Generation Fails

Symptoms: /api/labels/:id/pdf returns 500 error

Solutions:

  1. Cairo/Pango Missing:

The Dockerfile includes these, but verify in logs. Rebuild if fonts are missing.

  1. Memory Limit:

Increase Node.js memory:

CMD ["node", "--max-old-space-size=2048", "server.js"]
  1. Large Content:

Reduce content size or split across multiple pages.

Issue: QR Code Not Generating

Symptoms: /api/labels/qr returns error

Solutions:

  1. Invalid Data:

Ensure data field is not empty and is valid URL or text.

  1. Size Too Large:

QR code size must be reasonable (100-500 pixels). Larger sizes may timeout.

  1. Check Dependencies:

Verify qrcode package is installed:

Terminal window
npm list qrcode

Issue: File Upload Fails

Symptoms: Cannot upload images or logos

Solutions:

  1. Volume Not Mounted:

Verify persistent volume is attached at /app/public/uploads.

  1. Permissions:

Ensure directory is writable:

RUN mkdir -p /app/public/uploads && chown -R node:node /app
  1. File Size Limit:

Check MAX_FILE_SIZE environment variable:

MAX_FILE_SIZE=10485760

Issue: Slow Label Generation

Symptoms: API requests timeout or take too long

Solutions:

  1. Increase Resources:

Scale up container CPU and memory in Klutch.sh dashboard.

  1. Database Query Optimization:

Add indexes for frequently queried fields:

CREATE INDEX idx_labels_created_at ON labels(created_at DESC);
  1. Connection Pool:

Increase database connection pool size in server.js.

  1. Batch Processing:

For bulk operations, process in smaller batches with delays.

Issue: Database Connection Pool Exhausted

Symptoms: “sorry, too many clients already” error

Solutions:

  1. Increase Pool Size:
const pool = new Pool({
// ...
max: 50, // Increase from 20
});
  1. Release Connections:

Ensure all database queries properly release connections:

const client = await pool.connect();
try {
// Query here
} finally {
client.release(); // Always release
}
  1. Scale PostgreSQL:

Increase max connections in PostgreSQL configuration.

Additional Resources

  • PostgreSQL - Deploy PostgreSQL for E-Label data storage
  • Express.js - Learn more about Express.js deployment
  • MinIO - Alternative object storage for generated files

You now have E-Label running on Klutch.sh! Your digital label and QR code management system is ready to generate labels, track assets, and provide scalable labeling solutions for your organization. Configure templates, integrate with your existing systems via the API, and start creating professional labels with embedded QR codes and barcodes.