Overview
This guide covers deploying POS Kasir to production environments with security hardening, performance optimization, and monitoring setup.
Deployment Architecture
Recommended Setup
┌─────────────────┐
│ Load Balancer │
│ (Optional) │
└────────┬────────┘
│
┌────────▼────────┐
│ Reverse Proxy │
│ (Nginx/Caddy) │
└────────┬────────┘
│
┌────┴────┐
│ │
┌───▼──┐ ┌──▼────┐
│ Frontend│ │Backend│
│ :3000 │ │ :8080 │
└────────┘ └───┬───┘
│
┌──────▼──────┐
│ PostgreSQL │
└─────────────┘
Pre-Deployment Checklist
Infrastructure
Security
Monitoring
Server Requirements
Minimum Specifications
CPU : 2 cores
RAM : 2GB
Storage : 20GB SSD
OS : Ubuntu 22.04 LTS or similar
Network : 100 Mbps
Recommended Specifications
CPU : 4 cores
RAM : 4GB
Storage : 50GB SSD
OS : Ubuntu 22.04 LTS
Network : 1 Gbps
Installation Steps
1. Server Preparation
# Update system
sudo apt update && sudo apt upgrade -y
# Install dependencies
sudo apt install -y git curl wget software-properties-common
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$( uname -s )-$( uname -m )" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Verify installation
docker --version
docker-compose --version
2. Application Setup
# Create application directory
sudo mkdir -p /opt/pos-kasir
sudo chown $USER : $USER /opt/pos-kasir
cd /opt/pos-kasir
# Clone repository (or upload via CI/CD)
git clone https://github.com/your-org/pos-kasir.git .
# Create environment files
cp .env.example .env
cp web/.env.example web/.env
Edit /opt/pos-kasir/.env with production values:
# Production configuration
APP_ENV = production
APP_NAME = POS-Kasir
APP_PORT = 8080
COOKIE_DOMAIN = your-domain.com
WEB_FRONTEND_CROSS_ORIGIN = false
# Database (use managed PostgreSQL in production)
DB_HOST = your-postgres-host.com
DB_PORT = 5432
DB_USER = pos_kasir_prod
DB_PASSWORD = STRONG_RANDOM_PASSWORD_HERE
DB_NAME = pos_kasir_prod
DB_SSLMODE = require
DB_MAX_OPEN_CONNECTIONS = 25
DB_MAX_IDLE_CONNECTIONS = 5
DB_MAX_LIFETIME_MINUTES = 30
# Enable auto migrations (or run manually)
AUTO_MIGRATE = true
MIGRATIONS_PATH = ./sqlc/migrations
# Production logging
LOG_LEVEL = info
LOG_JSON_FORMAT = true
# JWT (generate strong secret)
JWT_SECRET = GENERATE_RANDOM_32_CHAR_STRING_HERE
JWT_DURATION_HOURS = 24
JWT_ISSUER = poskasir
# Midtrans production
MIDTRANS_SERVER_KEY = your-production-server-key
MIDTRANS_IS_PROD = true
# Cloudflare R2
R2_ACCOUNT_ID = your-account-id
R2_ACCESS_KEY = your-access-key
R2_SECRET_KEY = your-secret-key
R2_BUCKET = pos-kasir-prod
R2_PUBLIC_DOMAIN = https://cdn.your-domain.com
R2_EXPIRY_SECONDS = 3600
Never commit .env files to version control. Use environment variable injection from your hosting platform or secrets management system.
4. Database Setup
# Connect to PostgreSQL
psql -h your-postgres-host.com -U postgres
# Create database and user
CREATE DATABASE pos_kasir_prod ;
CREATE USER pos_kasir_prod WITH PASSWORD 'STRONG_PASSWORD' ;
GRANT ALL PRIVILEGES ON DATABASE pos_kasir_prod TO pos_kasir_prod ;
\q
# Test connection
psql -h your-postgres-host.com -U pos_kasir_prod -d pos_kasir_prod
If AUTO_MIGRATE=false, run migrations manually:
docker-compose run --rm backend ./main migrate
5. Build and Deploy
# Build images
docker-compose build --no-cache
# Start services
docker-compose up -d
# Verify services are running
docker-compose ps
docker-compose logs -f
6. Reverse Proxy Setup
Install and configure Nginx:
sudo apt install -y nginx
sudo systemctl enable nginx
Create /etc/nginx/sites-available/pos-kasir:
upstream backend {
server localhost:8080;
}
upstream frontend {
server localhost:3000;
}
server {
listen 80 ;
server_name your-domain.com www.your-domain.com;
# Redirect to HTTPS
return 301 https://$ server_name $ request_uri ;
}
server {
listen 443 ssl http2;
server_name your-domain.com www.your-domain.com;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on ;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Frontend
location / {
proxy_pass http://frontend;
proxy_http_version 1.1 ;
proxy_set_header Upgrade $ http_upgrade ;
proxy_set_header Connection 'upgrade' ;
proxy_set_header Host $ host ;
proxy_set_header X-Real-IP $ remote_addr ;
proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ;
proxy_set_header X-Forwarded-Proto $ scheme ;
proxy_cache_bypass $ http_upgrade ;
}
# Backend API
location /api {
proxy_pass http://backend;
proxy_http_version 1.1 ;
proxy_set_header Host $ host ;
proxy_set_header X-Real-IP $ remote_addr ;
proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ;
proxy_set_header X-Forwarded-Proto $ scheme ;
# Increase timeouts for long-running requests
proxy_connect_timeout 60s ;
proxy_send_timeout 60s ;
proxy_read_timeout 60s ;
}
# Health check endpoint
location /health {
proxy_pass http://backend/health;
access_log off ;
}
# Max upload size (adjust as needed)
client_max_body_size 10M ;
}
Enable site and restart Nginx:
sudo ln -s /etc/nginx/sites-available/pos-kasir /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
7. SSL Certificate Setup
Use Certbot for Let’s Encrypt:
# Install Certbot
sudo apt install -y certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
# Test auto-renewal
sudo certbot renew --dry-run
Security Hardening
Firewall Configuration
# Install UFW
sudo apt install -y ufw
# Allow SSH, HTTP, HTTPS
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable firewall
sudo ufw enable
sudo ufw status
Fail2ban Setup
# Install Fail2ban
sudo apt install -y fail2ban
# Create local configuration
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# Edit configuration
sudo nano /etc/fail2ban/jail.local
# Set: bantime = 3600, maxretry = 5
# Start service
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Environment Variable Security
In production, use secrets management instead of .env files:
AWS Secrets Manager
HashiCorp Vault
Docker Secrets
Kubernetes Secrets
Example using Docker Secrets:
# Create secrets
echo "your-jwt-secret" | docker secret create jwt_secret -
echo "your-db-password" | docker secret create db_password -
# Update docker-compose.yml to use secrets
Monitoring and Logging
Application Logs
# View real-time logs
docker-compose logs -f
# Export logs to file
docker-compose logs > /var/log/pos-kasir/app.log
# Set up log rotation
sudo nano /etc/logrotate.d/pos-kasir
Log rotation configuration:
/var/log/pos-kasir/*.log {
daily
rotate 14
compress
delaycompress
notifempty
create 0640 root root
sharedscripts
}
Health Monitoring
Create a systemd service for health checks:
sudo nano /etc/systemd/system/pos-kasir-health.service
[Unit]
Description =POS Kasir Health Check
After =network.target
[Service]
Type =oneshot
ExecStart =/usr/local/bin/pos-kasir-health-check.sh
[Install]
WantedBy =multi-user.target
Health check script (/usr/local/bin/pos-kasir-health-check.sh):
#!/bin/bash
HEALTH_URL = "http://localhost:8080/health"
RESPONSE = $( curl -s -o /dev/null -w "%{http_code}" $HEALTH_URL )
if [ $RESPONSE -ne 200 ]; then
echo "Health check failed with status $RESPONSE "
# Send alert (email, Slack, etc.)
docker-compose restart backend
fi
Set up monitoring with Prometheus and Grafana (optional):
# Add to docker-compose.yml
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana
ports:
- "3001:3000"
depends_on:
- prometheus
Backup and Recovery
Database Backup
# Create backup script
sudo nano /usr/local/bin/pos-kasir-backup.sh
#!/bin/bash
BACKUP_DIR = "/opt/backups/pos-kasir"
DATE = $( date +%Y%m%d_%H%M%S )
FILENAME = "pos_kasir_${ DATE }.sql.gz"
mkdir -p $BACKUP_DIR
# Dump database
PGPASSWORD = " $DB_PASSWORD " pg_dump -h $DB_HOST -U $DB_USER $DB_NAME | gzip > "${ BACKUP_DIR }/${ FILENAME }"
# Keep only last 30 days
find $BACKUP_DIR -type f -mtime +30 -delete
echo "Backup completed: ${ FILENAME }"
Schedule daily backups:
# Add to crontab
crontab -e
# Add: 0 2 * * * /usr/local/bin/pos-kasir-backup.sh
Recovery
# Restore from backup
gunzip < backup.sql.gz | psql -h $DB_HOST -U $DB_USER $DB_NAME
CI/CD Integration
GitHub Actions Example
name : Deploy to Production
on :
push :
branches : [ main ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- name : Deploy to server
uses : appleboy/ssh-action@master
with :
host : ${{ secrets.PROD_HOST }}
username : ${{ secrets.PROD_USER }}
key : ${{ secrets.PROD_SSH_KEY }}
script : |
cd /opt/pos-kasir
git pull origin main
docker-compose build --no-cache
docker-compose up -d
docker-compose logs --tail=50
Troubleshooting
Service Won’t Start
# Check logs
docker-compose logs backend
# Check port conflicts
sudo netstat -tulpn | grep :8080
# Restart services
docker-compose restart
High Memory Usage
# Check resource usage
docker stats
# Adjust resource limits in docker-compose.yml
# Restart services
docker-compose down && docker-compose up -d
Database Connection Issues
# Test database connectivity
telnet $DB_HOST $DB_PORT
# Verify SSL configuration
openssl s_client -connect $DB_HOST : $DB_PORT -starttls postgres
# Check environment variables
docker-compose exec backend env | grep DB_
Maintenance
Regular Updates
# Update system packages
sudo apt update && sudo apt upgrade -y
# Update Docker images
docker-compose pull
docker-compose up -d
# Clean up old images
docker system prune -a
Monitoring Checklist
Next Steps
Docker Deployment Learn Docker-specific deployment details
Environment Variables Complete environment configuration reference