Skip to main content

Overview

POS Kasir uses environment variables for configuration. This page documents all available variables with their purpose, valid values, and recommendations.
All configuration is read from .env files in the root directory for the backend and web/.env for the frontend.

Configuration File

Create your configuration by copying the example:
cp .env.example .env
Edit .env with your specific values before running the application.

Server & Application Config

Core application settings that control runtime behavior.
APP_ENV
string
default:"development"
required
Application environment mode.Valid values:
  • development - Development mode with debug features
  • production - Production mode with optimizations
Example:
APP_ENV=production
APP_NAME
string
default:"POS-Kasir"
required
Application name used in logs and responses.Example:
APP_NAME=POS-Kasir
APP_PORT
integer
default:"8080"
required
Port number for the backend server to listen on.Example:
APP_PORT=8080
When using Docker, this must match the internal port in docker-compose.yml.
Domain for setting cookies. Leave empty for localhost.Example:
# For production
COOKIE_DOMAIN=.yourdomain.com

# For localhost (development)
COOKIE_DOMAIN=
Use a leading dot (.yourdomain.com) to allow cookies across subdomains.
WEB_FRONTEND_CROSS_ORIGIN
boolean
default:"false"
Enable CORS for frontend requests.Valid values:
  • true - Allow cross-origin requests
  • false - Restrict to same origin
Example:
WEB_FRONTEND_CROSS_ORIGIN=false

Database Configuration

PostgreSQL database connection settings.
DB_HOST
string
required
PostgreSQL database host address.Example:
# External database
DB_HOST=postgres.example.com

# Docker service name
DB_HOST=postgres

# Localhost
DB_HOST=localhost
DB_PORT
integer
default:"5432"
required
PostgreSQL database port.Example:
DB_PORT=5432
DB_USER
string
required
Database username for authentication.Example:
DB_USER=pos_kasir
DB_PASSWORD
string
required
Database password for authentication.Example:
DB_PASSWORD=your-secure-password-here
Use a strong, randomly generated password in production. Never commit passwords to version control.
DB_NAME
string
required
Name of the PostgreSQL database.Example:
DB_NAME=pos_kasir
DB_SSLMODE
string
default:"disable"
required
SSL/TLS mode for database connections.Valid values:
  • disable - No SSL (development only)
  • require - Require SSL connection
  • verify-ca - Verify certificate authority
  • verify-full - Full certificate verification
Example:
# Development
DB_SSLMODE=disable

# Production (recommended)
DB_SSLMODE=require
Always use require or higher in production environments.

Connection Pool Settings

Optimize database performance with connection pooling.
DB_MAX_OPEN_CONNECTIONS
integer
default:"10"
Maximum number of open database connections.Recommendations:
  • Development: 10
  • Production (small): 25
  • Production (large): 50-100
Example:
DB_MAX_OPEN_CONNECTIONS=25
DB_MAX_IDLE_CONNECTIONS
integer
default:"2"
Maximum number of idle connections in the pool.Recommendations:
  • Should be less than DB_MAX_OPEN_CONNECTIONS
  • Typical value: 20-30% of max open connections
Example:
DB_MAX_IDLE_CONNECTIONS=5
DB_MAX_LIFETIME_MINUTES
integer
default:"10"
Maximum lifetime of a connection in minutes.Recommendations:
  • Development: 10 minutes
  • Production: 30-60 minutes
Example:
DB_MAX_LIFETIME_MINUTES=30

Migration Settings

Database schema migration configuration.
AUTO_MIGRATE
boolean
default:"false"
Automatically run database migrations on startup.Valid values:
  • true - Auto-run migrations (convenient for development)
  • false - Manual migration (recommended for production)
Example:
# Development
AUTO_MIGRATE=true

# Production (run migrations manually)
AUTO_MIGRATE=false
In production, set to false and run migrations manually to maintain control over schema changes.
MIGRATIONS_PATH
string
default:"./sqlc/migrations"
Path to database migration files.Example:
MIGRATIONS_PATH=./sqlc/migrations

Logger Configuration

Application logging settings.
LOG_LEVEL
string
default:"info"
Minimum log level to output.Valid values (in order of severity):
  • debug - Detailed debugging information
  • info - General informational messages
  • warn - Warning messages
  • error - Error messages only
Example:
# Development
LOG_LEVEL=debug

# Production
LOG_LEVEL=info
LOG_JSON_FORMAT
boolean
default:"false"
Output logs in JSON format for log aggregation systems.Valid values:
  • true - JSON format (recommended for production)
  • false - Human-readable format (better for development)
Example:
# Development
LOG_JSON_FORMAT=false

# Production (for log aggregation)
LOG_JSON_FORMAT=true

JWT Authentication

JSON Web Token configuration for user authentication.
JWT_SECRET
string
required
Secret key for signing JWT tokens.Requirements:
  • Minimum 32 characters
  • Use random, cryptographically secure string
  • Never reuse across environments
Example:
JWT_SECRET=your-very-long-random-secret-key-here-min-32-chars
Generate a secure secret:
openssl rand -base64 32
Never commit this value to version control. Use environment variable injection in production.
JWT_DURATION_HOURS
integer
default:"24"
Token expiration time in hours.Recommendations:
  • Web app: 24 hours
  • Mobile app: 168 hours (7 days)
  • API clients: 1 hour with refresh tokens
Example:
JWT_DURATION_HOURS=24
JWT_ISSUER
string
default:"poskasir"
JWT issuer claim for token validation.Example:
JWT_ISSUER=poskasir

Midtrans Payment Gateway

Configuration for Midtrans payment processing integration.
MIDTRANS_SERVER_KEY
string
required
Midtrans server key for API authentication.Where to find:
  1. Login to Midtrans Dashboard
  2. Go to Settings > Access Keys
  3. Copy Server Key (Sandbox or Production)
Example:
# Sandbox
MIDTRANS_SERVER_KEY=SB-Mid-server-xxxxxxxxxxxxxxxx

# Production
MIDTRANS_SERVER_KEY=Mid-server-xxxxxxxxxxxxxxxx
Keep this key secret. Never expose in client-side code or commit to version control.
MIDTRANS_IS_PROD
boolean
default:"false"
Use Midtrans production environment.Valid values:
  • true - Production mode (real transactions)
  • false - Sandbox mode (testing)
Example:
# Development/Testing
MIDTRANS_IS_PROD=false

# Production
MIDTRANS_IS_PROD=true
Always test thoroughly in sandbox mode before enabling production.

Cloudflare R2 Storage

Cloudflare R2 object storage configuration for file uploads.
R2_ACCOUNT_ID
string
required
Cloudflare account ID.Where to find:
  1. Login to Cloudflare Dashboard
  2. Go to R2 > Overview
  3. Copy Account ID
Example:
R2_ACCOUNT_ID=abc123def456ghi789
R2_ACCESS_KEY
string
required
R2 API access key ID.Where to find:
  1. Cloudflare Dashboard > R2
  2. Manage R2 API Tokens
  3. Create API token or use existing
  4. Copy Access Key ID
Example:
R2_ACCESS_KEY=a1b2c3d4e5f6g7h8i9j0
R2_SECRET_KEY
string
required
R2 API secret access key.Where to find:
  • Shown only once when creating API token
  • Cannot be retrieved later - must regenerate if lost
Example:
R2_SECRET_KEY=your-secret-access-key-here
Store this securely. It’s only shown once during token creation.
R2_BUCKET
string
default:"pos-kasir"
required
R2 bucket name for storing files.Example:
# Development
R2_BUCKET=pos-kasir-dev

# Production
R2_BUCKET=pos-kasir-prod
Use separate buckets for different environments to prevent data mixing.
R2_PUBLIC_DOMAIN
string
required
Public domain for accessing R2 files.Setup:
  1. Create R2 bucket
  2. Enable public access or custom domain
  3. Configure DNS (if using custom domain)
Example:
# R2 default domain
R2_PUBLIC_DOMAIN=https://pub-xxxxx.r2.dev

# Custom domain
R2_PUBLIC_DOMAIN=https://cdn.yourdomain.com
R2_EXPIRY_SECONDS
integer
default:"3600"
Pre-signed URL expiration time in seconds.Recommendations:
  • Public files: 3600 (1 hour)
  • Private files: 900 (15 minutes)
  • Temporary uploads: 300 (5 minutes)
Example:
R2_EXPIRY_SECONDS=3600

Environment-Specific Examples

Development Environment

# Server & Application
APP_ENV=development
APP_NAME=POS-Kasir
APP_PORT=8080
COOKIE_DOMAIN=
WEB_FRONTEND_CROSS_ORIGIN=false

# Database (local)
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=pos_kasir_dev
DB_SSLMODE=disable
DB_MAX_OPEN_CONNECTIONS=10
DB_MAX_IDLE_CONNECTIONS=2
DB_MAX_LIFETIME_MINUTES=10

# Migration
AUTO_MIGRATE=true
MIGRATIONS_PATH=./sqlc/migrations

# Logger
LOG_LEVEL=debug
LOG_JSON_FORMAT=false

# JWT (use different secret than production)
JWT_SECRET=dev-secret-key-min-32-characters-long
JWT_DURATION_HOURS=24
JWT_ISSUER=poskasir

# Midtrans (sandbox)
MIDTRANS_SERVER_KEY=SB-Mid-server-xxxxxxxxxxxxxxxx
MIDTRANS_IS_PROD=false

# R2 (dev bucket)
R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY=your-access-key
R2_SECRET_KEY=your-secret-key
R2_BUCKET=pos-kasir-dev
R2_PUBLIC_DOMAIN=https://pub-xxxxx.r2.dev
R2_EXPIRY_SECONDS=3600

Production Environment

# Server & Application
APP_ENV=production
APP_NAME=POS-Kasir
APP_PORT=8080
COOKIE_DOMAIN=.yourdomain.com
WEB_FRONTEND_CROSS_ORIGIN=false

# Database (managed PostgreSQL)
DB_HOST=prod-postgres.example.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

# Migration (manual in production)
AUTO_MIGRATE=false
MIGRATIONS_PATH=./sqlc/migrations

# Logger (JSON for log aggregation)
LOG_LEVEL=info
LOG_JSON_FORMAT=true

# JWT (strong random secret)
JWT_SECRET=GENERATE_STRONG_RANDOM_SECRET_MIN_32_CHARS
JWT_DURATION_HOURS=24
JWT_ISSUER=poskasir

# Midtrans (production)
MIDTRANS_SERVER_KEY=Mid-server-PRODUCTION_KEY_HERE
MIDTRANS_IS_PROD=true

# R2 (production bucket)
R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY=your-prod-access-key
R2_SECRET_KEY=your-prod-secret-key
R2_BUCKET=pos-kasir-prod
R2_PUBLIC_DOMAIN=https://cdn.yourdomain.com
R2_EXPIRY_SECONDS=3600

Security Best Practices

Follow these security practices for environment variables:

1. Never Commit Secrets

Add .env to .gitignore:
# .gitignore
.env
.env.local
.env.production
*.env

2. Use Strong Random Values

Generate secure secrets:
# JWT secret
openssl rand -base64 32

# General secrets
openssl rand -hex 32

# UUID-based
uuidgen

3. Environment Separation

Use different values for each environment:
  • Development: .env.development
  • Staging: .env.staging
  • Production: .env.production

4. Secrets Management

In production, use secrets management services:
  • AWS: AWS Secrets Manager
  • Google Cloud: Secret Manager
  • Azure: Key Vault
  • HashiCorp: Vault
  • Docker: Docker Secrets
  • Kubernetes: Kubernetes Secrets

5. Access Control

Restrict file permissions:
chmod 600 .env
chown $USER:$USER .env

6. Audit Regularly

  • Review access to secrets monthly
  • Rotate credentials quarterly
  • Monitor for unauthorized access
  • Use different credentials per environment

Validation

Validate your configuration before deployment:
# Check required variables are set
grep -v '^#' .env | grep -v '^$' | while read line; do
  key=$(echo $line | cut -d'=' -f1)
  value=$(echo $line | cut -d'=' -f2-)
  if [ -z "$value" ]; then
    echo "Missing value for: $key"
  fi
done

# Test database connection
psql "postgresql://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=$DB_SSLMODE"

# Test application startup
docker-compose up --abort-on-container-exit

Troubleshooting

Variable Not Loading

# Verify .env file exists
ls -la .env

# Check file permissions
ls -l .env

# Verify variable syntax (no spaces around =)
cat .env | grep -E '\s=\s'

# Test loading
set -a; source .env; set +a
echo $APP_ENV

Docker Not Reading .env

# Verify env_file path in docker-compose.yml
cat docker-compose.yml | grep env_file

# Rebuild containers
docker-compose down
docker-compose up -d --build

# Check loaded variables
docker-compose exec backend env

Database Connection Fails

# Test connection manually
telnet $DB_HOST $DB_PORT

# Verify SSL mode
psql "postgresql://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=require"

# Check firewall rules
sudo ufw status

Next Steps

Docker Deployment

Deploy using Docker Compose

Production Guide

Production deployment best practices

Build docs developers (and LLMs) love