Skip to content

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:

Terminal window
mkdir digicard-klutch
cd digicard-klutch
git init

Step 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 directory
WORKDIR /app
# Install build dependencies
RUN apk add --no-cache python3 make g++ git cairo-dev jpeg-dev pango-dev giflib-dev
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application source
COPY . .
# Build frontend assets
RUN npm run build:frontend 2>/dev/null || echo "No frontend build needed"
# Build backend if TypeScript
RUN npm run build:backend 2>/dev/null || echo "No backend build needed"
# Production stage
FROM node:20-alpine
# Install runtime dependencies
RUN apk add --no-cache \
tini \
curl \
cairo \
jpeg \
pango \
giflib \
&& rm -rf /var/cache/apk/*
# Set working directory
WORKDIR /app
# Copy from builder
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/public ./public
COPY --from=builder /app/package*.json ./
# Create directories for user data
RUN mkdir -p /app/uploads /app/qrcodes /app/data /app/logs && \
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
# Use tini as entrypoint for proper signal handling
ENTRYPOINT ["/sbin/tini", "--"]
# Start application
CMD ["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 middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
}));
// Rate limiting
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, please try again later'
});
app.use('/api/', limiter);
// Body parsing middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// CORS configuration
const corsOptions = {
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
credentials: true
};
app.use(require('cors')(corsOptions));
// Static files
app.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 services
const dbService = new DatabaseService(config.database);
const cardService = new CardService(dbService);
const qrcodeService = new QRCodeService();
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: process.env.NODE_ENV
});
});
// API routes
app.use('/api', routes);
// Serve frontend (SPA fallback)
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../public/index.html'));
});
// Error handling middleware
app.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 server
const 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 shutdown
process.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 table
CREATE 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 table
CREATE 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 table
CREATE 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 table
CREATE 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 analytics
CREATE 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 submissions
CREATE 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 integrations
CREATE 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 indexes
CREATE 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:

Terminal window
# Server Configuration
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
# Application Settings
APP_BASE_URL=https://example-app.klutch.sh
APP_NAME=Digicard
SUPPORT_EMAIL=support@example.com
ALLOWED_ORIGINS=https://example-app.klutch.sh,https://yourdomain.com
# Database Configuration (PostgreSQL)
DB_TYPE=postgres
DB_HOST=postgres-app.klutch.sh
DB_PORT=8000
DB_NAME=digicard
DB_USER=digicard
DB_PASSWORD=your-secure-password
DB_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
# Security
JWT_SECRET=your-secure-jwt-secret-key-minimum-32-characters-long
JWT_EXPIRES_IN=30d
# File Upload Settings
MAX_FILE_SIZE=5242880
ALLOWED_FILE_TYPES=image/jpeg,image/png,image/gif,image/webp
# Email Configuration (SMTP)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@example.com
SMTP_PASSWORD=smtp-password
SMTP_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=
# Analytics
ENABLE_ANALYTICS=true
ENABLE_GEOLOCATION=false
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100

Step 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
```bash
npm install
npm run dev

Production Deployment

Build and run with Docker:

Terminal window
docker build -t digicard .
docker run -p 3000:3000 --env-file .env digicard

API Endpoints

  • GET /health - Health check
  • GET /api/cards/:slug - Get card by slug
  • POST /api/cards - Create new card
  • PUT /api/cards/:id - Update card
  • DELETE /api/cards/:id - Delete card
  • POST /api/cards/:id/contact - Submit contact form
  • GET /api/cards/:id/analytics - Get card analytics

License

MIT

### Step 8: Push to GitHub
Commit your files and push to GitHub:
```bash
git add .
git commit -m "Initial Digicard setup for Klutch.sh deployment"
git remote add origin https://github.com/yourusername/digicard.git
git branch -M main
git push -u origin main

Setting Up Database

Digicard requires PostgreSQL or MongoDB. This guide covers PostgreSQL setup.

PostgreSQL Setup

    1. Deploy PostgreSQL on Klutch.sh

      Create a new app for PostgreSQL using the official PostgreSQL Docker image:

      FROM postgres:15-alpine
      ENV POSTGRES_DB=digicard
      ENV POSTGRES_USER=digicard
      # Set POSTGRES_PASSWORD via environment variables in Klutch.sh
      EXPOSE 5432
    2. 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)
    3. Set PostgreSQL Traffic Type

      • Traffic Type: Select TCP
      • Internal Port: 5432
      • External Port: 8000 (this is where you’ll connect from Digicard)
    4. Note Connection Details

      Your PostgreSQL connection will be available at:

      Host: your-postgres-app.klutch.sh
      Port: 8000
      Database: digicard
      Username: digicard
      Password: (set in environment variables)

Deploying to Klutch.sh

Now that your database is set up, deploy the Digicard application.

Deployment Steps

    1. Log in to Klutch.sh

      Navigate to klutch.sh/app and sign in to your account.

    2. Create a New Project

      Go to Create Project and give your project a meaningful name (e.g., “Digicard Platform”).

    3. Create the Digicard App

      Navigate to Create App and configure the following settings:

    4. Select Your Repository

      • Choose GitHub as your Git source
      • Select the repository containing your Dockerfile
      • Choose the branch you want to deploy (usually main)
    5. 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)
    6. Set Environment Variables

      Add the following environment variables (use the connection details from your PostgreSQL deployment):

      Server Configuration:

      • NODE_ENV: production
      • PORT: 3000
      • HOST: 0.0.0.0

      Application Settings:

      • APP_BASE_URL: Your Digicard URL (e.g., https://example-app.klutch.sh)
      • APP_NAME: Digicard
      • SUPPORT_EMAIL: Your support email address
      • ALLOWED_ORIGINS: Your Digicard URL (e.g., https://example-app.klutch.sh)

      Database Configuration:

      • DB_TYPE: postgres
      • DB_HOST: Your PostgreSQL app hostname (e.g., postgres-app.klutch.sh)
      • DB_PORT: 8000 (external port for TCP traffic)
      • DB_NAME: digicard
      • DB_USER: digicard
      • DB_PASSWORD: Strong password for database
      • DB_SSL: true

      Security:

      • JWT_SECRET: Generate a secure 32+ character random string
      • JWT_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 address
      • SMTP_PORT: SMTP port (usually 587 or 465)
      • SMTP_USER: SMTP username
      • SMTP_PASSWORD: SMTP password
      • SMTP_FROM: Sender email address

      Analytics:

      • ENABLE_ANALYTICS: true
    7. 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
    8. 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)
    9. 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
    10. 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.

    11. Access Your Digicard Instance

      Once deployment is complete, navigate to your assigned URL:

      https://example-app.klutch.sh

      You 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

    1. 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"
      }'
    2. 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.

    3. 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"
      }
      ]
      }'
    4. View Your Card

      Navigate to:

      https://example-app.klutch.sh/john-doe

      Your digital business card is now live and shareable!

    5. 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:

VariableDescriptionRequiredDefault
NODE_ENVNode environmentYesproduction
PORTApplication portYes3000
HOSTBind host addressYes0.0.0.0
APP_BASE_URLPublic base URLYesNone
APP_NAMEApplication nameNoDigicard
SUPPORT_EMAILSupport email addressNoNone
ALLOWED_ORIGINSCORS allowed originsNo*
DB_TYPEDatabase type (postgres/mongodb)Yespostgres
DB_HOSTDatabase hostYesNone
DB_PORTDatabase portYes5432
DB_NAMEDatabase nameYesdigicard
DB_USERDatabase usernameYesNone
DB_PASSWORDDatabase passwordYesNone
DB_SSLEnable SSL for databaseNofalse
JWT_SECRETSecret key for JWT tokensYesNone
JWT_EXPIRES_INJWT expiration timeNo30d
MAX_FILE_SIZEMaximum upload size (bytes)No5242880
ALLOWED_FILE_TYPESAllowed MIME typesNoimage/*
SMTP_HOSTSMTP server hostNoNone
SMTP_PORTSMTP server portNo587
SMTP_USERSMTP usernameNoNone
SMTP_PASSWORDSMTP passwordNoNone
SMTP_FROMSender email addressNoNone
ENABLE_ANALYTICSEnable view trackingNotrue
RATE_LIMIT_WINDOW_MSRate limit windowNo900000
RATE_LIMIT_MAX_REQUESTSMax requests per windowNo100

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_ORIGINS to 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 /health endpoint 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

    1. Database Backups

      Set up automated PostgreSQL backups:

      Terminal window
      # Manual backup
      pg_dump -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME > digicard-backup-$(date +%Y%m%d).sql

      Schedule regular backups using cron or a backup service.

    2. File Backups

      Regularly backup the uploads and QR codes directories:

      Terminal window
      # Backup uploads
      tar -czf uploads-backup-$(date +%Y%m%d).tar.gz /app/uploads
      # Backup QR codes
      tar -czf qrcodes-backup-$(date +%Y%m%d).tar.gz /app/qrcodes
    3. 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
    4. Restore Process

      To restore from backup:

      Terminal window
      # Restore database
      psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME < digicard-backup.sql
      # Restore files
      tar -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:

src/themes/custom-theme.ts
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 URL
const 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 analytics
app.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 view
await 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_TOKEN

Endpoints

Authentication:

  • POST /api/auth/register - Register new user
  • POST /api/auth/login - Login and get JWT token
  • POST /api/auth/logout - Logout (invalidate token)
  • POST /api/auth/forgot-password - Request password reset
  • POST /api/auth/reset-password - Reset password with token

Cards:

  • GET /api/cards - List all public cards
  • GET /api/cards/:slug - Get card by slug
  • POST /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 file
  • GET /api/cards/:id/qrcode - Get QR code image

Contact:

  • POST /api/cards/:id/contact - Submit contact form
  • GET /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_SSL setting matches PostgreSQL configuration

Image Upload Failures

  • Volume mounted: Verify /app/uploads volume is correctly attached
  • Permissions: Check directory permissions in container
  • Disk space: Ensure sufficient space in uploads volume
  • File size limit: Check MAX_FILE_SIZE environment 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/qrcodes directory 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_public is 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:

  1. Add domain in Klutch.sh: Navigate to your app settings and add custom domain
  2. Configure DNS: Point your domain to the provided Klutch.sh address
  3. Update environment variable: Set APP_BASE_URL to your custom domain
  4. Update CORS: Add custom domain to ALLOWED_ORIGINS
  5. Verify SSL: Klutch.sh automatically provisions SSL certificates
  6. 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 card
function applyTemplate(card: Card, templateName: string) {
const template = templates[templateName];
return {
...card,
...template
};
}

Additional Resources


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.