Deployment
Overview
The SuperAdmin server is deployed as a separate service from the main application, sharing the same database and environment configuration but running with restricted access and minimal modules.
Environment Variables
The SuperAdmin server uses the same environment variables as the main application.
Required Variables
# Core Application
LOG_LEVEL=debug
SESSION_DURATION=720h
DOMAIN=superadmin.yourdomain.com
GO_APP_ENV=production
# Database (shared with main app)
DB_HOST=localhost
DB_PORT=5432
DB_NAME=iota_erp
DB_USER=postgres
DB_PASSWORD=postgres
# Server
PORT=3000
# Authentication
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REDIRECT_URL=https://superadmin.yourdomain.com/auth/google/callback
Important Notes
- Update
DOMAINto match your SuperAdmin deployment URL - Update
GOOGLE_REDIRECT_URLto match superadmin domain - Both main app and superadmin can use same
DB_*variables (same database) PORTcan be same in containers (different host ports in docker-compose)
Optional Variables
# OpenTelemetry (distributed tracing)
OTEL_ENABLED=true
OTEL_SERVICE_NAME=iota-superadmin
OTEL_TEMPO_URL=http://tempo:4318
# Logging
LOG_FORMAT=json
LOG_OUTPUT=stdout
Local Development
Prerequisites
- Go 1.23.2+
- PostgreSQL 13+
- Make
- Docker (optional)
Setup Steps
- Clone Repository:
git clone https://github.com/iota-uz/iota-sdk.git cd iota-sdk - Configure Environment:
cp .env.example .env.superadmin # Edit .env.superadmin with your settings - Run Database Migrations:
make db migrate up - Create Super Admin User:
-- Run this SQL in your database INSERT INTO users ( id, email, first_name, last_name, type, tenant_id, created_at, updated_at ) VALUES ( gen_random_uuid(), 'admin@yourdomain.com', 'Super', 'Admin', 'superadmin', NULL, NOW(), NOW() ); - Build and Run: ```bash
Build
go build -o run_superadmin cmd/superadmin/main.go
Run with environment file
set -a source .env.superadmin set +a ./run_superadmin
6. **Access Server:**
http://localhost:3000
### Using DevHub
If using [DevHub](https://github.com/iota-uz/devhub):
```bash
# Add superadmin service
devhub add superadmin go run cmd/superadmin/main.go
# Start all services including superadmin
devhub start
# View superadmin logs
devhub logs superadmin
Docker Build
Build Docker Image
# Build SuperAdmin image
docker build -f Dockerfile.superadmin -t iota-sdk-superadmin:latest .
# Tag for registry
docker tag iota-sdk-superadmin:latest your-registry/iota-sdk-superadmin:latest
# Push to registry
docker push your-registry/iota-sdk-superadmin:latest
Dockerfile.superadmin
# Build stage
FROM golang:1.23.2 AS builder
WORKDIR /app
COPY . .
# Generate templates
RUN go install github.com/a-h/templ/cmd/templ@latest
RUN templ generate
# Build
RUN CGO_ENABLED=0 GOOS=linux go build -o run_superadmin cmd/superadmin/main.go
# Runtime stage
FROM alpine:3.18
RUN apk add --no-cache ca-certificates
WORKDIR /home/iota-user
COPY --from=builder /app/run_superadmin .
EXPOSE 3000
CMD ["./run_superadmin"]
Docker Compose Deployment
docker-compose.superadmin.yml
version: '3.8'
services:
superadmin:
build:
context: .
dockerfile: Dockerfile.superadmin
container_name: iota-superadmin
ports:
- "3001:3000" # Different host port from main app
environment:
- LOG_LEVEL=info
- SESSION_DURATION=720h
- DOMAIN=admin.localhost
- GO_APP_ENV=development
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=iota_erp
- DB_USER=postgres
- DB_PASSWORD=postgres
- PORT=3000
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
- GOOGLE_REDIRECT_URL=http://localhost:3001/auth/google/callback
depends_on:
- db
restart: unless-stopped
networks:
- iota-network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 40s
# Shared PostgreSQL
db:
image: postgres:13-alpine
container_name: iota-postgres
environment:
POSTGRES_DB: iota_erp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- iota-network
restart: unless-stopped
networks:
iota-network:
driver: bridge
volumes:
postgres_data:
Run Docker Compose
# Start services
docker-compose -f docker-compose.superadmin.yml up -d
# View logs
docker-compose -f docker-compose.superadmin.yml logs -f superadmin
# Stop services
docker-compose -f docker-compose.superadmin.yml down
Railway Deployment
Railway Configuration
Railway requires two separate services (main app and superadmin) since each needs independent configuration.
Service 1: Main Application
# railway.toml
[build]
builder = "dockerfile"
dockerfilePath = "Dockerfile"
# Startup is handled by Dockerfile CMD via scripts/start.sh
# This ensures consistent behavior between local Docker and Railway
Service 2: Super Admin
# railway.superadmin.toml
[build]
builder = "dockerfile"
dockerfilePath = "Dockerfile.superadmin"
[deploy]
startCommand = "command migrate up && run_superadmin"
Railway Environment Setup
- Create two services in Railway:
iota-main- Main applicationiota-superadmin- SuperAdmin service
- Link shared PostgreSQL:
- Both services use same database
- Configure via environment variables
- Configure domains:
- Main app:
app.yourdomain.com - SuperAdmin:
admin.yourdomain.com
- Main app:
- Set environment variables:
- Both services share
DB_*variables - Each service has unique
DOMAINand OAuth redirect URL - Copy other variables identically
- Both services share
Deployment Command
# Deploy via Railway CLI
railway up --service iota-superadmin
Kubernetes Deployment
Deployment YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: iota-superadmin
namespace: iota-production
spec:
replicas: 2
selector:
matchLabels:
app: iota-superadmin
template:
metadata:
labels:
app: iota-superadmin
spec:
containers:
- name: superadmin
image: your-registry/iota-sdk-superadmin:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
name: http
env:
- name: LOG_LEVEL
value: "info"
- name: PORT
value: "3000"
- name: GO_APP_ENV
value: "production"
- name: DOMAIN
value: "admin.yourdomain.com"
- name: SESSION_DURATION
value: "720h"
- name: DB_HOST
valueFrom:
secretKeyRef:
name: iota-db-secret
key: host
- name: DB_PORT
value: "5432"
- name: DB_NAME
valueFrom:
secretKeyRef:
name: iota-db-secret
key: database
- name: DB_USER
valueFrom:
secretKeyRef:
name: iota-db-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: iota-db-secret
key: password
- name: GOOGLE_CLIENT_ID
valueFrom:
secretKeyRef:
name: iota-oauth-secret
key: google-client-id
- name: GOOGLE_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: iota-oauth-secret
key: google-client-secret
- name: GOOGLE_REDIRECT_URL
value: "https://admin.yourdomain.com/auth/google/callback"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
---
apiVersion: v1
kind: Service
metadata:
name: iota-superadmin-service
namespace: iota-production
spec:
selector:
app: iota-superadmin
ports:
- protocol: TCP
port: 80
targetPort: 3000
name: http
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: iota-superadmin-ingress
namespace: iota-production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- admin.yourdomain.com
secretName: superadmin-tls
rules:
- host: admin.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: iota-superadmin-service
port:
number: 80
Deploy to Kubernetes
# Apply deployment
kubectl apply -f k8s/superadmin-deployment.yaml
# Check rollout status
kubectl rollout status deployment/iota-superadmin -n iota-production
# View logs
kubectl logs -f deployment/iota-superadmin -n iota-production
# Port forward for testing
kubectl port-forward service/iota-superadmin-service 3000:80 -n iota-production
Health Checks
Health Endpoint
curl http://localhost:3000/health
Response:
{
"status": "healthy",
"database": "connected",
"modules": ["core", "superadmin"],
"uptime_seconds": 3600,
"timestamp": "2025-09-30T10:00:00Z"
}
Monitoring & Logging
Application Logs
Configure log level via environment:
LOG_LEVEL=debug # debug, info, warn, error
Structured Logging Example:
logger.WithFields(logrus.Fields{
"super_admin_id": userId,
"tenant_id": tenantId,
"action": "tenant_created",
}).Info("Super admin created new tenant")
OpenTelemetry Tracing
Enable distributed tracing:
OTEL_ENABLED=true
OTEL_SERVICE_NAME=iota-superadmin
OTEL_TEMPO_URL=http://tempo:4318
Metrics Tracking
Recommended metrics:
- Request Metrics: Requests/sec, response times (p50, p95, p99), error rates
- Database Metrics: Connection pool usage, query times, active connections
- Business Metrics: Tenant operations, super admin logins, API usage
Security Considerations
SSL/TLS
- Production: HTTPS required (TLS termination at load balancer)
- Development: HTTP acceptable for local testing
- Certificates: Use Let’s Encrypt (automated via cert-manager in K8s)
Database Security
- Connection Security:
- Use SSL/TLS for database connections
- Store credentials in secrets management
- Rotate passwords regularly
- Query Safety:
- All queries parameterized (no concatenation)
- Prepared statements for all queries
- Input validation on all endpoints
Network Security
- Deployment Isolation:
- Deploy on separate subdomain:
admin.yourdomain.com - Consider VPN or IP whitelist for additional security
- Network policies in K8s to restrict traffic
- Deploy on separate subdomain:
- Super Admin Account Security:
- Create via database only (never API)
- Use strong, unique passwords
- Enable MFA if available
- Minimal number of super admin accounts
Audit Logging
All operations logged:
logger.WithFields(logrus.Fields{
"super_admin_id": superAdminID,
"action": "delete_tenant",
"tenant_id": tenantID,
"ip_address": r.RemoteAddr,
"user_agent": r.UserAgent(),
"timestamp": time.Now(),
}).Warn("Super admin deleted tenant")
Troubleshooting
403 Forbidden on all routes
Cause: User is not a super admin.
Solution:
-- Check user type
SELECT id, email, type FROM users WHERE email = 'your-email@domain.com';
-- Update user to superadmin
UPDATE users SET type = 'superadmin' WHERE email = 'your-email@domain.com';
Database connection errors
Cause: Database not accessible or incorrect credentials.
Solution:
# Test database connection
psql "host=$DB_HOST port=$DB_PORT dbname=$DB_NAME user=$DB_USER password=$DB_PASSWORD"
# Check environment variables
env | grep DB_
Module registration errors
Cause: Module dependencies not satisfied.
Solution:
go mod download
go build -o run_superadmin cmd/superadmin/main.go
Session validation failures
Cause: Session cookie not being set or expired.
Solution:
- Check
SESSION_DURATIONconfiguration - Verify cookie domain matches deployment URL
- Clear browser cookies and re-login
- Check clock synchronization between services
Enable Debug Mode
LOG_LEVEL=debug ./run_superadmin
Backup & Recovery
Database Backup
# Backup SuperAdmin data
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > backup_$(date +%Y%m%d).sql
# Restore from backup
psql -h $DB_HOST -U $DB_USER -d $DB_NAME < backup_20250101.sql
Service Recovery
- SuperAdmin service can be restarted without affecting main application
- Shared database ensures data consistency
- No shared state or caching (stateless design)
- Multiple replicas ensure high availability
Performance Tuning
Database Connection Pool
// Recommended settings
MaxOpenConns: 25
MaxIdleConns: 5
MaxConnLifetime: 5 minutes
Query Optimization
- Create indexes on frequently queried columns
- Use EXPLAIN ANALYZE for slow queries
- Cache analytics results (5-minute TTL)
- Batch operations where possible
Caching Strategy
- Dashboard metrics: 5-minute cache
- Tenant list: 1-minute cache
- User searches: No cache (real-time)