Deploying a Digicard App
Introduction
Digicard is a modern digital business card platform that revolutionizes professional networking by transforming traditional paper cards into dynamic, shareable digital profiles. Perfect for professionals, entrepreneurs, and businesses looking to make lasting connections, Digicard provides an elegant solution for creating, managing, and sharing contact information in today’s digital-first world.
Digicard stands out with its:
- Dynamic Digital Cards: Create beautiful, customizable digital business cards with rich media support
- QR Code Generation: Instant QR codes for seamless contact sharing at events and meetings
- vCard Export: Standard vCard format support for easy import into any contact management system
- Contact Analytics: Track when and where your card is viewed and shared
- Multi-Profile Support: Manage multiple cards for different roles or businesses
- Social Media Integration: Link all your professional profiles in one place
- Custom Branding: Personalize cards with logos, colors, and custom domains
- Contact Form: Built-in contact forms for lead generation and inquiries
- Real-Time Updates: Update your information once and all shared cards reflect changes instantly
- NFC Support: Compatible with NFC-enabled devices for tap-to-share functionality
- Privacy Controls: Granular control over what information you share and with whom
- API Access: RESTful API for integration with CRM systems and other tools
This comprehensive guide walks you through deploying Digicard on Klutch.sh using Docker, covering installation, database setup, file storage, environment configuration, and production best practices.
Prerequisites
Before you begin deploying Digicard, ensure you have the following:
- A Klutch.sh account
- A GitHub account with a repository for your Digicard project
- Docker installed locally for testing (optional but recommended)
- Basic understanding of Docker, databases, and web applications
- A domain name for your Digicard instance (recommended for production)
Understanding Digicard Architecture
Before deployment, it’s helpful to understand Digicard’s architecture:
- Application Server: Node.js/Express backend handling API requests and business logic
- Database: PostgreSQL or MongoDB for storing user profiles, cards, and analytics
- File Storage: Local storage or S3-compatible object storage for profile images and assets
- QR Code Generator: Server-side QR code generation library for card sharing
- Frontend: React or Vue.js SPA for card creation and management interface
- Cache Layer: Redis for session management and analytics caching (optional)
For Klutch.sh deployment, we’ll containerize Digicard with its dependencies, utilizing persistent volumes for data and media storage.
Installation and Setup
Step 1: Create Your Project Directory
First, create a new directory for your Digicard deployment project:
mkdir digicard-klutchcd digicard-klutchgit initStep 2: Create the Dockerfile
Create a Dockerfile in your project root. This example builds a full-stack Digicard application:
FROM node:20-alpine AS builder
# Set working directoryWORKDIR /app
# Install build dependenciesRUN apk add --no-cache python3 make g++ git cairo-dev jpeg-dev pango-dev giflib-dev
# Copy package filesCOPY package*.json ./
# Install dependenciesRUN npm ci --only=production
# Copy application sourceCOPY . .
# Build frontend assetsRUN npm run build:frontend 2>/dev/null || echo "No frontend build needed"
# Build backend if TypeScriptRUN npm run build:backend 2>/dev/null || echo "No backend build needed"
# Production stageFROM node:20-alpine
# Install runtime dependenciesRUN apk add --no-cache \ tini \ curl \ cairo \ jpeg \ pango \ giflib \ && rm -rf /var/cache/apk/*
# Set working directoryWORKDIR /app
# Copy from builderCOPY --from=builder /app/node_modules ./node_modulesCOPY --from=builder /app/dist ./distCOPY --from=builder /app/public ./publicCOPY --from=builder /app/package*.json ./
# Create directories for user dataRUN mkdir -p /app/uploads /app/qrcodes /app/data /app/logs && \ chown -R node:node /app
# Switch to non-root userUSER node
# Expose application portEXPOSE 3000
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1
# Use tini as entrypoint for proper signal handlingENTRYPOINT ["/sbin/tini", "--"]
# Start applicationCMD ["node", "dist/server.js"]Step 3: Create Application Source Files
Create the backend application structure:
package.json:
{ "name": "digicard", "version": "1.0.0", "description": "Digital business card platform", "main": "dist/server.js", "scripts": { "start": "node dist/server.js", "dev": "ts-node src/server.ts", "build:backend": "tsc", "build:frontend": "cd frontend && npm run build", "test": "jest" }, "dependencies": { "express": "^4.18.2", "pg": "^8.11.3", "mongoose": "^8.0.0", "multer": "^1.4.5-lts.1", "sharp": "^0.33.0", "qrcode": "^1.5.3", "bcrypt": "^5.1.1", "jsonwebtoken": "^9.0.2", "cors": "^2.8.5", "dotenv": "^16.3.1", "express-validator": "^7.0.1", "express-rate-limit": "^7.1.5", "helmet": "^7.1.0", "uuid": "^9.0.1", "nodemailer": "^6.9.7", "vcard-creator": "^1.0.1" }, "devDependencies": { "@types/node": "^20.8.0", "@types/express": "^4.17.20", "@types/multer": "^1.4.11", "typescript": "^5.2.2", "ts-node": "^10.9.1" }}src/server.ts:
import express from 'express';import { config } from './config';import { DatabaseService } from './services/database';import { CardService } from './services/card';import { QRCodeService } from './services/qrcode';import routes from './routes';import helmet from 'helmet';import rateLimit from 'express-rate-limit';import path from 'path';
const app = express();
// Security middlewareapp.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], }, },}));
// Rate limitingconst 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, please try again later'});app.use('/api/', limiter);
// Body parsing middlewareapp.use(express.json({ limit: '10mb' }));app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// CORS configurationconst corsOptions = { origin: process.env.ALLOWED_ORIGINS?.split(',') || '*', credentials: true};app.use(require('cors')(corsOptions));
// Static filesapp.use('/uploads', express.static(path.join(__dirname, '../uploads')));app.use('/qrcodes', express.static(path.join(__dirname, '../qrcodes')));app.use(express.static(path.join(__dirname, '../public')));
// Initialize servicesconst dbService = new DatabaseService(config.database);const cardService = new CardService(dbService);const qrcodeService = new QRCodeService();
// Health check endpointapp.get('/health', (req, res) => { res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), environment: process.env.NODE_ENV });});
// API routesapp.use('/api', routes);
// Serve frontend (SPA fallback)app.get('*', (req, res) => { res.sendFile(path.join(__dirname, '../public/index.html'));});
// Error handling middlewareapp.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { console.error('Error:', err); res.status(err.status || 500).json({ error: err.message || 'Internal server error', ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) });});
// Start serverconst PORT = process.env.PORT || 3000;const HOST = process.env.HOST || '0.0.0.0';
async function start() { try { // Initialize database await dbService.connect(); await dbService.migrate();
// Start server app.listen(PORT, HOST, () => { console.log(`Digicard server running on http://${HOST}:${PORT}`); console.log(`Environment: ${process.env.NODE_ENV}`); }); } catch (error) { console.error('Failed to start server:', error); process.exit(1); }}
// Graceful shutdownprocess.on('SIGTERM', async () => { console.log('SIGTERM received, shutting down gracefully'); await dbService.disconnect(); process.exit(0);});
process.on('SIGINT', async () => { console.log('SIGINT received, shutting down gracefully'); await dbService.disconnect(); process.exit(0);});
start();src/config.ts:
export const config = { database: { type: process.env.DB_TYPE || 'postgres', // 'postgres' or 'mongodb' host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), database: process.env.DB_NAME || 'digicard', user: process.env.DB_USER || 'digicard', password: process.env.DB_PASSWORD || '', ssl: process.env.DB_SSL === 'true' }, jwt: { secret: process.env.JWT_SECRET || 'change-me-in-production', expiresIn: process.env.JWT_EXPIRES_IN || '30d' }, upload: { maxFileSize: parseInt(process.env.MAX_FILE_SIZE || '5242880'), // 5MB default allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] }, qrcode: { errorCorrectionLevel: 'M', type: 'image/png', width: 300, margin: 1 }, email: { host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT || '587'), user: process.env.SMTP_USER, password: process.env.SMTP_PASSWORD, from: process.env.SMTP_FROM || 'noreply@digicard.app' }, app: { baseUrl: process.env.APP_BASE_URL || 'https://example-app.klutch.sh', name: process.env.APP_NAME || 'Digicard', supportEmail: process.env.SUPPORT_EMAIL || 'support@digicard.app' }};src/services/qrcode.ts:
import QRCode from 'qrcode';import { promises as fs } from 'fs';import path from 'path';
export class QRCodeService { private qrCodeDir: string;
constructor() { this.qrCodeDir = path.join(__dirname, '../../qrcodes'); this.ensureQRCodeDir(); }
private async ensureQRCodeDir() { try { await fs.mkdir(this.qrCodeDir, { recursive: true }); } catch (error) { console.error('Failed to create QR code directory:', error); } }
async generateQRCode(data: string, filename: string): Promise<string> { const filePath = path.join(this.qrCodeDir, `${filename}.png`);
try { await QRCode.toFile(filePath, data, { errorCorrectionLevel: 'M', type: 'png', width: 300, margin: 1, color: { dark: '#000000', light: '#FFFFFF' } });
return `/qrcodes/${filename}.png`; } catch (error) { console.error('QR code generation failed:', error); throw new Error('Failed to generate QR code'); } }
async generateDataURL(data: string): Promise<string> { try { return await QRCode.toDataURL(data, { errorCorrectionLevel: 'M', width: 300 }); } catch (error) { console.error('QR code data URL generation failed:', error); throw new Error('Failed to generate QR code data URL'); } }}Step 4: Create Database Migration Script
Create a migrations/001_initial.sql file:
-- Users tableCREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, role VARCHAR(50) DEFAULT 'user', email_verified BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
-- Digital cards tableCREATE TABLE IF NOT EXISTS cards ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, slug VARCHAR(255) UNIQUE NOT NULL, title VARCHAR(255) NOT NULL, subtitle VARCHAR(255), bio TEXT, profile_image VARCHAR(500), cover_image VARCHAR(500), theme VARCHAR(50) DEFAULT 'default', is_active BOOLEAN DEFAULT TRUE, is_public BOOLEAN DEFAULT TRUE, view_count INTEGER DEFAULT 0, qr_code_url VARCHAR(500), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
-- Contact information tableCREATE TABLE IF NOT EXISTS contact_info ( id SERIAL PRIMARY KEY, card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE, type VARCHAR(50) NOT NULL, -- email, phone, website, address, etc. label VARCHAR(100), value TEXT NOT NULL, icon VARCHAR(100), display_order INTEGER DEFAULT 0, is_visible BOOLEAN DEFAULT TRUE);
-- Social links tableCREATE TABLE IF NOT EXISTS social_links ( id SERIAL PRIMARY KEY, card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE, platform VARCHAR(50) NOT NULL, -- linkedin, twitter, github, etc. url VARCHAR(500) NOT NULL, display_order INTEGER DEFAULT 0);
-- Card views analyticsCREATE TABLE IF NOT EXISTS card_views ( id SERIAL PRIMARY KEY, card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE, ip_address INET, user_agent TEXT, referer VARCHAR(500), country VARCHAR(100), city VARCHAR(100), viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
-- Contact form submissionsCREATE TABLE IF NOT EXISTS contact_submissions ( id SERIAL PRIMARY KEY, card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, subject VARCHAR(255), message TEXT NOT NULL, status VARCHAR(50) DEFAULT 'unread', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
-- API tokens for integrationsCREATE TABLE IF NOT EXISTS api_tokens ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, token_hash VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(255) NOT NULL, last_used_at TIMESTAMP, expires_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
-- Create indexesCREATE INDEX idx_cards_user_id ON cards(user_id);CREATE INDEX idx_cards_slug ON cards(slug);CREATE INDEX idx_cards_is_public ON cards(is_public);CREATE INDEX idx_contact_info_card_id ON contact_info(card_id);CREATE INDEX idx_social_links_card_id ON social_links(card_id);CREATE INDEX idx_card_views_card_id ON card_views(card_id);CREATE INDEX idx_card_views_viewed_at ON card_views(viewed_at);CREATE INDEX idx_contact_submissions_card_id ON contact_submissions(card_id);Step 5: Create Frontend Interface
Create a basic frontend in public/index.html:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Digicard - Digital Business Cards</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; }
.container { background: white; border-radius: 20px; padding: 40px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); max-width: 500px; width: 100%; }
h1 { color: #333; margin-bottom: 10px; font-size: 2.5em; text-align: center; }
.tagline { text-align: center; color: #666; margin-bottom: 30px; font-size: 1.1em; }
.card-preview { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; padding: 30px; color: white; margin-bottom: 30px; text-align: center; }
.profile-image { width: 120px; height: 120px; border-radius: 50%; background: white; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; font-size: 3em; color: #667eea; }
.card-name { font-size: 1.8em; font-weight: bold; margin-bottom: 5px; }
.card-title { font-size: 1.1em; opacity: 0.9; margin-bottom: 15px; }
.contact-buttons { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.contact-btn { background: rgba(255, 255, 255, 0.2); border: 2px solid white; color: white; padding: 10px 20px; border-radius: 25px; text-decoration: none; font-weight: 500; transition: all 0.3s ease; }
.contact-btn:hover { background: white; color: #667eea; }
.features { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 30px; }
.feature { padding: 15px; background: #f8f9fa; border-radius: 10px; text-align: center; }
.feature-icon { font-size: 2em; margin-bottom: 10px; }
.feature-text { font-size: 0.9em; color: #666; }
.cta-button { width: 100%; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 10px; font-size: 1.1em; font-weight: bold; cursor: pointer; transition: transform 0.3s ease; }
.cta-button:hover { transform: translateY(-2px); }
.api-info { margin-top: 20px; padding: 15px; background: #e8f4f8; border-left: 4px solid #667eea; border-radius: 5px; font-size: 0.9em; color: #555; } </style></head><body> <div class="container"> <h1>💳 Digicard</h1> <p class="tagline">Your digital business card platform</p>
<div class="card-preview"> <div class="profile-image">👤</div> <div class="card-name">John Doe</div> <div class="card-title">Product Designer</div> <div class="contact-buttons"> <a href="#" class="contact-btn">📧 Email</a> <a href="#" class="contact-btn">📱 Phone</a> <a href="#" class="contact-btn">🌐 Website</a> </div> </div>
<div class="features"> <div class="feature"> <div class="feature-icon">📱</div> <div class="feature-text">QR Code Sharing</div> </div> <div class="feature"> <div class="feature-icon">📊</div> <div class="feature-text">View Analytics</div> </div> <div class="feature"> <div class="feature-icon">🎨</div> <div class="feature-text">Custom Themes</div> </div> <div class="feature"> <div class="feature-icon">🔗</div> <div class="feature-text">Social Links</div> </div> </div>
<button class="cta-button" onclick="window.location.href='/api/docs'"> Get Started </button>
<div class="api-info"> <strong>API Status:</strong> Ready<br> <strong>Version:</strong> 1.0.0<br> <strong>Endpoint:</strong> <code>/api/v1</code> </div> </div>
<script> // Check API health fetch('/health') .then(res => res.json()) .then(data => console.log('Health check:', data)) .catch(err => console.error('Health check failed:', err)); </script></body></html>Step 6: Create Environment Configuration
Create a .env.example file to document required environment variables:
# Server ConfigurationNODE_ENV=productionPORT=3000HOST=0.0.0.0
# Application SettingsAPP_BASE_URL=https://example-app.klutch.shAPP_NAME=DigicardSUPPORT_EMAIL=support@example.comALLOWED_ORIGINS=https://example-app.klutch.sh,https://yourdomain.com
# Database Configuration (PostgreSQL)DB_TYPE=postgresDB_HOST=postgres-app.klutch.shDB_PORT=8000DB_NAME=digicardDB_USER=digicardDB_PASSWORD=your-secure-passwordDB_SSL=true
# Alternative: MongoDB Configuration# DB_TYPE=mongodb# DB_HOST=mongodb-app.klutch.sh# DB_PORT=8000# DB_NAME=digicard# DB_USER=digicard# DB_PASSWORD=your-secure-password
# SecurityJWT_SECRET=your-secure-jwt-secret-key-minimum-32-characters-longJWT_EXPIRES_IN=30d
# File Upload SettingsMAX_FILE_SIZE=5242880ALLOWED_FILE_TYPES=image/jpeg,image/png,image/gif,image/webp
# Email Configuration (SMTP)SMTP_HOST=smtp.example.comSMTP_PORT=587SMTP_USER=noreply@example.comSMTP_PASSWORD=smtp-passwordSMTP_FROM=noreply@example.com
# Optional: Redis for Caching# REDIS_HOST=redis-app.klutch.sh# REDIS_PORT=8000# REDIS_PASSWORD=
# Optional: S3 for File Storage# S3_ENABLED=false# S3_BUCKET=digicard-uploads# S3_REGION=us-east-1# S3_ACCESS_KEY_ID=# S3_SECRET_ACCESS_KEY=# S3_ENDPOINT=
# AnalyticsENABLE_ANALYTICS=trueENABLE_GEOLOCATION=false
# Rate LimitingRATE_LIMIT_WINDOW_MS=900000RATE_LIMIT_MAX_REQUESTS=100Step 7: Create README Documentation
Create a README.md file:
# Digicard - Digital Business Card Platform
Modern, self-hosted digital business card platform with QR code generation, analytics, and contact management.
## Features
- Digital business card creation and management- QR code generation for easy sharing- vCard export for contact import- View analytics and tracking- Custom branding and themes- Contact form submissions- RESTful API- Multi-profile support
## Requirements
- Node.js 18+- PostgreSQL 13+ or MongoDB 5+- Storage for uploaded files
## Environment Variables
See `.env.example` for all required configuration.
## Local Development
```bashnpm installnpm run devProduction Deployment
Build and run with Docker:
docker build -t digicard .docker run -p 3000:3000 --env-file .env digicardAPI Endpoints
GET /health- Health checkGET /api/cards/:slug- Get card by slugPOST /api/cards- Create new cardPUT /api/cards/:id- Update cardDELETE /api/cards/:id- Delete cardPOST /api/cards/:id/contact- Submit contact formGET /api/cards/:id/analytics- Get card analytics
License
MIT
### Step 8: Push to GitHub
Commit your files and push to GitHub:
```bashgit add .git commit -m "Initial Digicard setup for Klutch.sh deployment"git remote add origin https://github.com/yourusername/digicard.gitgit branch -M maingit push -u origin mainSetting Up Database
Digicard requires PostgreSQL or MongoDB. This guide covers PostgreSQL setup.
PostgreSQL Setup
-
Deploy PostgreSQL on Klutch.sh
Create a new app for PostgreSQL using the official PostgreSQL Docker image:
FROM postgres:15-alpineENV POSTGRES_DB=digicardENV POSTGRES_USER=digicard# Set POSTGRES_PASSWORD via environment variables in Klutch.shEXPOSE 5432 -
Configure PostgreSQL Volume
Attach a persistent volume to ensure your database persists:
- Mount Path:
/var/lib/postgresql/data - Size: At least 10GB (recommended 20GB+ for production)
- Mount Path:
-
Set PostgreSQL Traffic Type
- Traffic Type: Select TCP
- Internal Port:
5432 - External Port:
8000(this is where you’ll connect from Digicard)
-
Note Connection Details
Your PostgreSQL connection will be available at:
Host: your-postgres-app.klutch.shPort: 8000Database: digicardUsername: digicardPassword: (set in environment variables)
Deploying to Klutch.sh
Now that your database is set up, deploy the Digicard application.
Deployment Steps
-
Log in to Klutch.sh
Navigate to klutch.sh/app and sign in to your account.
-
Create a New Project
Go to Create Project and give your project a meaningful name (e.g., “Digicard Platform”).
-
Create the Digicard App
Navigate to Create App and configure the following settings:
-
Select Your Repository
- Choose GitHub as your Git source
- Select the repository containing your Dockerfile
- Choose the branch you want to deploy (usually
main)
-
Configure Traffic Type
- Traffic Type: Select HTTP (Digicard serves a web application via HTTP)
- Internal Port: Set to
3000(the default port that Digicard listens on)
-
Set Environment Variables
Add the following environment variables (use the connection details from your PostgreSQL deployment):
Server Configuration:
NODE_ENV:productionPORT:3000HOST:0.0.0.0
Application Settings:
APP_BASE_URL: Your Digicard URL (e.g.,https://example-app.klutch.sh)APP_NAME:DigicardSUPPORT_EMAIL: Your support email addressALLOWED_ORIGINS: Your Digicard URL (e.g.,https://example-app.klutch.sh)
Database Configuration:
DB_TYPE:postgresDB_HOST: Your PostgreSQL app hostname (e.g.,postgres-app.klutch.sh)DB_PORT:8000(external port for TCP traffic)DB_NAME:digicardDB_USER:digicardDB_PASSWORD: Strong password for databaseDB_SSL:true
Security:
JWT_SECRET: Generate a secure 32+ character random stringJWT_EXPIRES_IN:30d
File Upload:
MAX_FILE_SIZE:5242880(5MB in bytes)ALLOWED_FILE_TYPES:image/jpeg,image/png,image/gif,image/webp
Email Configuration (optional but recommended):
SMTP_HOST: Your SMTP server addressSMTP_PORT: SMTP port (usually587or465)SMTP_USER: SMTP usernameSMTP_PASSWORD: SMTP passwordSMTP_FROM: Sender email address
Analytics:
ENABLE_ANALYTICS:true
-
Attach Persistent Volumes
Digicard requires persistent storage for user uploads and generated QR codes:
Uploads Volume:
- Click “Add Volume”
- Mount Path:
/app/uploads - Size: 20GB+ (adjust based on expected usage)
QR Codes Volume:
- Click “Add Volume”
- Mount Path:
/app/qrcodes - Size: 5GB (for generated QR codes)
Logs Volume (optional):
- Click “Add Volume”
- Mount Path:
/app/logs - Size: 5GB
-
Configure Resource Allocation
For Digicard with image processing:
- CPU: Minimum 1 CPU, recommended 2 CPUs for production
- Memory: Minimum 1GB RAM, recommended 2GB+ for image processing
- Instances: Start with 1 instance (scale horizontally as needed)
-
Deploy Your Application
Click “Create” to start the deployment. Klutch.sh will:
- Automatically detect your Dockerfile in the repository root
- Build the Docker image with Digicard
- Attach the persistent volumes
- Configure networking to PostgreSQL
- Start your Digicard container
- Assign a URL for external access
-
Wait for Deployment
The initial deployment may take 3-5 minutes as Digicard:
- Installs Node.js dependencies
- Compiles TypeScript to JavaScript
- Connects to PostgreSQL and runs migrations
- Starts the Express server
Monitor the logs in the Klutch.sh dashboard to track progress.
-
Access Your Digicard Instance
Once deployment is complete, navigate to your assigned URL:
https://example-app.klutch.shYou should see the Digicard landing page with the card preview interface.
Initial Configuration and First Card
After your Digicard instance is running, create your first digital business card:
Creating Your First Card
-
Register an Account
Visit your Digicard URL and register for an account using the API:
Terminal window curl -X POST https://example-app.klutch.sh/api/auth/register \-H "Content-Type: application/json" \-d '{"email": "your@email.com","password": "secure-password","name": "Your Name"}' -
Login to Get JWT Token
Terminal window curl -X POST https://example-app.klutch.sh/api/auth/login \-H "Content-Type: application/json" \-d '{"email": "your@email.com","password": "secure-password"}'Save the returned JWT token for subsequent requests.
-
Create Your First Digital Card
Terminal window curl -X POST https://example-app.klutch.sh/api/cards \-H "Content-Type: application/json" \-H "Authorization: Bearer YOUR_JWT_TOKEN" \-d '{"slug": "john-doe","title": "John Doe","subtitle": "Product Designer","bio": "Passionate about creating beautiful user experiences","theme": "default","is_public": true,"contact_info": [{"type": "email","label": "Email","value": "john@example.com","icon": "📧"},{"type": "phone","label": "Phone","value": "+1234567890","icon": "📱"}],"social_links": [{"platform": "linkedin","url": "https://linkedin.com/in/johndoe"},{"platform": "twitter","url": "https://twitter.com/johndoe"}]}' -
View Your Card
Navigate to:
https://example-app.klutch.sh/john-doeYour digital business card is now live and shareable!
-
Download QR Code
The QR code is automatically generated and accessible at:
https://example-app.klutch.sh/qrcodes/john-doe.png
Environment Variables Reference
Complete reference of Digicard environment variables:
| Variable | Description | Required | Default |
|---|---|---|---|
NODE_ENV | Node environment | Yes | production |
PORT | Application port | Yes | 3000 |
HOST | Bind host address | Yes | 0.0.0.0 |
APP_BASE_URL | Public base URL | Yes | None |
APP_NAME | Application name | No | Digicard |
SUPPORT_EMAIL | Support email address | No | None |
ALLOWED_ORIGINS | CORS allowed origins | No | * |
DB_TYPE | Database type (postgres/mongodb) | Yes | postgres |
DB_HOST | Database host | Yes | None |
DB_PORT | Database port | Yes | 5432 |
DB_NAME | Database name | Yes | digicard |
DB_USER | Database username | Yes | None |
DB_PASSWORD | Database password | Yes | None |
DB_SSL | Enable SSL for database | No | false |
JWT_SECRET | Secret key for JWT tokens | Yes | None |
JWT_EXPIRES_IN | JWT expiration time | No | 30d |
MAX_FILE_SIZE | Maximum upload size (bytes) | No | 5242880 |
ALLOWED_FILE_TYPES | Allowed MIME types | No | image/* |
SMTP_HOST | SMTP server host | No | None |
SMTP_PORT | SMTP server port | No | 587 |
SMTP_USER | SMTP username | No | None |
SMTP_PASSWORD | SMTP password | No | None |
SMTP_FROM | Sender email address | No | None |
ENABLE_ANALYTICS | Enable view tracking | No | true |
RATE_LIMIT_WINDOW_MS | Rate limit window | No | 900000 |
RATE_LIMIT_MAX_REQUESTS | Max requests per window | No | 100 |
Production Best Practices
Security Recommendations
- HTTPS Only: Always use HTTPS for your Digicard instance. Klutch.sh provides automatic TLS.
- Strong Secrets: Use a password manager to generate strong JWT secrets (minimum 32 characters).
- Environment Variables: Store all sensitive credentials as environment variables in Klutch.sh.
- Database Security: Use strong passwords for PostgreSQL and restrict access to database ports.
- Input Validation: Validate all user inputs on both client and server side.
- File Upload Security: Implement virus scanning for uploaded files in production.
- Rate Limiting: Configure appropriate rate limits to prevent abuse.
- CORS Configuration: Set
ALLOWED_ORIGINSto only include your trusted domains. - Content Security Policy: Configure CSP headers to prevent XSS attacks.
- Regular Updates: Keep Node.js, dependencies, and base images updated.
Performance Optimization
- Image Optimization: Use Sharp library to automatically optimize uploaded images.
- CDN Integration: Use a CDN for static assets and uploaded files.
- Database Indexing: Ensure proper indexes on frequently queried columns.
- Caching: Implement Redis caching for frequently accessed cards.
- Connection Pooling: Configure appropriate database connection pool sizes.
- Lazy Loading: Implement lazy loading for card images on the frontend.
- QR Code Caching: Cache generated QR codes to avoid regeneration.
- API Response Caching: Cache API responses for public cards.
- Image Compression: Compress images on upload to reduce storage and bandwidth.
- Database Query Optimization: Use proper indexes and optimize N+1 queries.
Monitoring and Maintenance
Monitor your Digicard deployment for:
- Application Health: Use the
/healthendpoint for uptime monitoring - Card View Analytics: Track card view counts and traffic patterns
- Upload Storage: Monitor disk usage for uploads and QR codes
- Database Performance: Query response times and connection pool usage
- API Response Times: Track average response times for API endpoints
- Error Rates: Monitor application errors and exceptions
- Memory Usage: Track Node.js memory consumption over time
- Failed Logins: Monitor for suspicious authentication attempts
- File Upload Failures: Track and alert on upload errors
- Email Delivery: Monitor SMTP success rates
Backup and Disaster Recovery
-
Database Backups
Set up automated PostgreSQL backups:
Terminal window # Manual backuppg_dump -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME > digicard-backup-$(date +%Y%m%d).sqlSchedule regular backups using cron or a backup service.
-
File Backups
Regularly backup the uploads and QR codes directories:
Terminal window # Backup uploadstar -czf uploads-backup-$(date +%Y%m%d).tar.gz /app/uploads# Backup QR codestar -czf qrcodes-backup-$(date +%Y%m%d).tar.gz /app/qrcodes -
Volume Snapshots
Utilize Klutch.sh volume snapshot features to backup:
/app/uploads(user profile images and assets)/app/qrcodes(generated QR codes)- PostgreSQL data volume
-
Restore Process
To restore from backup:
Terminal window # Restore databasepsql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME < digicard-backup.sql# Restore filestar -xzf uploads-backup.tar.gz -C /tar -xzf qrcodes-backup.tar.gz -C /
Advanced Features and Customization
Custom Card Themes
Create custom themes for digital cards by extending the theme system:
export const customTheme = { name: 'custom', colors: { primary: '#667eea', secondary: '#764ba2', background: '#ffffff', text: '#333333', accent: '#f59e0b' }, fonts: { heading: 'Inter, sans-serif', body: 'Inter, sans-serif' }, borderRadius: '15px', shadows: { card: '0 10px 30px rgba(0, 0, 0, 0.1)', button: '0 4px 12px rgba(0, 0, 0, 0.15)' }};NFC Tag Integration
For physical NFC tags that link to digital cards:
// Generate NFC-compatible URLconst nfcUrl = `${config.app.baseUrl}/${card.slug}?ref=nfc`;
// Write to NFC tag (frontend implementation)const writeNFCTag = async (url: string) => { if ('NDEFReader' in window) { const ndef = new NDEFReader(); await ndef.write({ records: [{ recordType: "url", data: url }] }); }};vCard Export
Generate vCard files for contact import:
import { vCard } from 'vcard-creator';
export function generateVCard(card: Card): string { const vcard = new vCard();
vcard .addName(card.title) .addJobtitle(card.subtitle) .addNote(card.bio) .addPhoto(card.profile_image, 'JPEG');
// Add contact info card.contact_info.forEach(contact => { if (contact.type === 'email') { vcard.addEmail(contact.value); } else if (contact.type === 'phone') { vcard.addPhoneNumber(contact.value, 'WORK'); } else if (contact.type === 'website') { vcard.addURL(contact.value); } });
// Add social links card.social_links.forEach(link => { vcard.addSocial(link.url, link.platform); });
return vcard.toString();}Analytics Dashboard
Implement detailed analytics for card views:
// Get card analyticsapp.get('/api/cards/:id/analytics', async (req, res) => { const { id } = req.params; const { startDate, endDate } = req.query;
const analytics = await db.query(` SELECT DATE(viewed_at) as date, COUNT(*) as views, COUNT(DISTINCT ip_address) as unique_visitors, country, city FROM card_views WHERE card_id = $1 AND viewed_at BETWEEN $2 AND $3 GROUP BY DATE(viewed_at), country, city ORDER BY date DESC `, [id, startDate, endDate]);
res.json(analytics);});Webhook Notifications
Send webhook notifications for card events:
async function sendWebhook(url: string, event: string, data: any) { try { await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Digicard-Event': event }, body: JSON.stringify({ event, timestamp: new Date().toISOString(), data }) }); } catch (error) { console.error('Webhook delivery failed:', error); }}
// Trigger on card viewawait sendWebhook(user.webhook_url, 'card.viewed', { card_id: card.id, viewer_country: geoData.country});API Documentation
Authentication
All authenticated endpoints require a JWT token in the Authorization header:
Authorization: Bearer YOUR_JWT_TOKENEndpoints
Authentication:
POST /api/auth/register- Register new userPOST /api/auth/login- Login and get JWT tokenPOST /api/auth/logout- Logout (invalidate token)POST /api/auth/forgot-password- Request password resetPOST /api/auth/reset-password- Reset password with token
Cards:
GET /api/cards- List all public cardsGET /api/cards/:slug- Get card by slugPOST /api/cards- Create new card (authenticated)PUT /api/cards/:id- Update card (authenticated)DELETE /api/cards/:id- Delete card (authenticated)GET /api/cards/:id/analytics- Get card analytics (authenticated)GET /api/cards/:id/vcard- Download vCard fileGET /api/cards/:id/qrcode- Get QR code image
Contact:
POST /api/cards/:id/contact- Submit contact formGET /api/contact-submissions- List submissions (authenticated)PUT /api/contact-submissions/:id- Mark as read (authenticated)
Profile:
GET /api/profile- Get user profile (authenticated)PUT /api/profile- Update profile (authenticated)POST /api/profile/avatar- Upload avatar (authenticated)
API Tokens:
GET /api/tokens- List API tokens (authenticated)POST /api/tokens- Create new token (authenticated)DELETE /api/tokens/:id- Revoke token (authenticated)
Scaling Your Digicard Instance
As your user base grows, you may need to scale your Digicard deployment:
Vertical Scaling
Increase resources for your existing instance:
- Upgrade CPU: Move to 2, 4, or more CPUs for better performance
- Increase Memory: Scale to 4GB, 8GB, or more RAM for image processing
- Expand Storage: Increase volume sizes as uploads grow
Horizontal Scaling
For high-traffic deployments:
- Multiple Instances: Run multiple Digicard app instances behind a load balancer
- Shared Storage: Use S3-compatible object storage for uploads (accessible by all instances)
- Database Read Replicas: Use PostgreSQL read replicas for read-heavy workloads
- Redis Caching: Add Redis for session management and card caching
- CDN: Offload static assets and uploads to a CDN
S3 Integration for Scalability
Configure S3-compatible storage for uploads:
import AWS from 'aws-sdk';
const s3 = new AWS.S3({ accessKeyId: process.env.S3_ACCESS_KEY_ID, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, endpoint: process.env.S3_ENDPOINT, region: process.env.S3_REGION});
async function uploadToS3(file: Express.Multer.File): Promise<string> { const key = `uploads/${Date.now()}-${file.originalname}`;
await s3.upload({ Bucket: process.env.S3_BUCKET!, Key: key, Body: file.buffer, ContentType: file.mimetype, ACL: 'public-read' }).promise();
return `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${key}`;}Troubleshooting
Cannot Access Digicard
- Check app status: Verify the app is running in Klutch.sh dashboard
- Verify port: Ensure internal port is set to
3000 - Traffic type: Confirm HTTP traffic type is selected
- Review logs: Check application logs for startup errors
- Database connectivity: Verify PostgreSQL is accessible
Database Connection Errors
- Connection refused: Verify PostgreSQL is running and accessible on port 8000
- Authentication failed: Check database credentials in environment variables
- Database not found: Ensure database exists; create with
createdb digicard - Network issues: Verify both apps can communicate on Klutch.sh network
- SSL errors: Verify
DB_SSLsetting matches PostgreSQL configuration
Image Upload Failures
- Volume mounted: Verify
/app/uploadsvolume is correctly attached - Permissions: Check directory permissions in container
- Disk space: Ensure sufficient space in uploads volume
- File size limit: Check
MAX_FILE_SIZEenvironment variable - MIME type: Verify file type is in
ALLOWED_FILE_TYPES
QR Code Generation Fails
- Cairo dependencies: Ensure cairo libraries are installed in Docker image
- Write permissions: Check
/app/qrcodesdirectory is writable - Disk space: Verify sufficient space in QR codes volume
- Library errors: Check logs for node-canvas or qrcode library errors
Card Not Displaying
- Slug conflict: Ensure card slug is unique
- Privacy settings: Check if card
is_publicis set to true - Database query: Verify card exists in database
- Frontend routing: Check frontend routing configuration
- CORS errors: Verify CORS settings allow your domain
Slow Performance
- Database queries: Review slow query logs and add indexes
- Image size: Implement image compression for uploads
- Caching: Add Redis caching for frequently accessed cards
- CDN: Use CDN for static assets and uploads
- Resource limits: Check CPU and memory usage, scale if needed
Advanced Configuration
Custom Domain Setup
To use a custom domain for your Digicard instance:
- Add domain in Klutch.sh: Navigate to your app settings and add custom domain
- Configure DNS: Point your domain to the provided Klutch.sh address
- Update environment variable: Set
APP_BASE_URLto your custom domain - Update CORS: Add custom domain to
ALLOWED_ORIGINS - Verify SSL: Klutch.sh automatically provisions SSL certificates
- Redeploy: Restart your app to pick up new configuration
Email Notifications
Configure email notifications for contact form submissions:
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT || '587'), auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASSWORD }});
async function notifyCardOwner(card: Card, submission: ContactSubmission) { await transporter.sendMail({ from: process.env.SMTP_FROM, to: card.user.email, subject: `New contact from ${submission.name}`, html: ` <h2>New Contact Submission</h2> <p><strong>Name:</strong> ${submission.name}</p> <p><strong>Email:</strong> ${submission.email}</p> <p><strong>Subject:</strong> ${submission.subject}</p> <p><strong>Message:</strong></p> <p>${submission.message}</p> ` });}Geolocation for Analytics
Add geolocation to track where cards are viewed:
import geoip from 'geoip-lite';
async function trackCardView(cardId: number, req: express.Request) { const ip = req.ip || req.connection.remoteAddress; const geo = geoip.lookup(ip);
await db.query(` INSERT INTO card_views (card_id, ip_address, user_agent, country, city, viewed_at) VALUES ($1, $2, $3, $4, $5, NOW()) `, [ cardId, ip, req.headers['user-agent'], geo?.country || null, geo?.city || null ]);}Custom Card Templates
Create reusable card templates:
const templates = { professional: { theme: 'default', layout: 'centered', sections: ['header', 'bio', 'contact', 'social'] }, creative: { theme: 'gradient', layout: 'split', sections: ['header', 'portfolio', 'contact', 'social'] }, minimal: { theme: 'mono', layout: 'simple', sections: ['header', 'contact'] }};
// Apply template to new cardfunction applyTemplate(card: Card, templateName: string) { const template = templates[templateName]; return { ...card, ...template };}Additional Resources
- Node.js Official Documentation
- PostgreSQL Documentation
- Express.js Guide
- node-qrcode Library
- Sharp Image Processing
- Klutch.sh Volumes Guide
- Klutch.sh Networking Guide
- Klutch.sh Custom Domains Guide
Conclusion
Deploying Digicard on Klutch.sh with Docker provides a powerful, self-hosted platform for creating and sharing digital business cards. By following this comprehensive guide, you’ve set up a production-ready Digicard instance with QR code generation, analytics tracking, contact management, and proper security measures. Your digital card platform is now ready to revolutionize how you and your users network professionally. Remember to monitor performance, maintain regular backups, keep dependencies updated, and scale resources as your user base grows. With Digicard deployed on Klutch.sh, you have complete control over your digital networking platform while enjoying the benefits of modern cloud infrastructure.