Useful Shell Commands
A cheat sheet for day-to-day CTMS server operations.
SSH
# Connect with key file
ssh -i ~/path/to/key.pem root@<server-ip>
# Connect with password
ssh root@<server-ip>
File Transfer & Extraction
# Upload file to server
scp -i ~/path/to/key.pem ./zynctl-bundle-*.tar.gz root@<server-ip>:~/
# Download file from server
scp -i ~/path/to/key.pem root@<server-ip>:/opt/ctms-deployment/.env.production ./
# Extract tar.gz
tar xzf zynctl-bundle-*.tar.gz
# Extract to a specific directory
tar xzf zynctl-bundle-*.tar.gz -C /opt/
# List contents without extracting
tar tzf zynctl-bundle-*.tar.gz
# Create tar.gz
tar czf backup.tar.gz /opt/ctms-deployment/
Docker Containers
# Running containers — clean table
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | sort
# All containers (including stopped)
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | sort
# Count: running / stopped / total
echo "Running: $(docker ps -q | wc -l) Stopped: $(docker ps -aq --filter status=exited | wc -l) Total: $(docker ps -aq | wc -l)"
# Resource usage
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" | sort
Docker Logs
# Follow logs
docker logs -f ctms-zynexa
# Last 100 lines
docker logs --tail 100 ctms-zynexa
# Logs since last 5 minutes
docker logs --since 5m ctms-zynexa
# Search for errors
docker logs ctms-zynexa 2>&1 | grep -i error
Docker Compose (CTMS)
cd /opt/ctms-deployment
DC="docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.production"
# Start all
$DC --profile all up -d
# Restart a single service (picks up env changes)
$DC up -d zynexa
# Stop everything
$DC --profile all down
# Pull latest images
$DC pull
# Status
$DC --profile all ps
# Logs for multiple services
$DC logs -f --tail 50 zynexa sublink api-gateway
# Shell into a container
docker exec -it ctms-zynexa sh
# Check env vars
docker exec ctms-zynexa env | sort
Health Check (All Services)
for svc in "Zynexa:3000" "Sublink:3001" "Gateway:9080" "Cube:4000" "MCP:8006" "ODM:8001" "OpenObserve:5080"; do
name="${svc%%:*}"; port="${svc##*:}"
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "http://127.0.0.1:$port" 2>/dev/null)
[[ "$status" =~ ^(200|301|302|404)$ ]] && echo "✅ $name :$port" || echo "❌ $name :$port ($status)"
done
Supabase & Frappe
# Supabase logs
cd /opt/ctms-deployment/supabase && docker compose logs -f --tail 50 auth rest
# Frappe logs
cd /opt/ctms-deployment/frappe-marley-health && docker compose logs -f --tail 50 backend
# Frappe bench command
docker exec frappe-marley-health-backend-1 bench --site frontend list-apps
# Generate Frappe API token
docker exec frappe-marley-health-backend-1 bench --site frontend execute \
frappe.core.doctype.user.user.generate_keys --args "['Administrator']"
Disk & System
# Disk usage
df -h /
# Docker disk usage
docker system df
# Reclaim unused images/volumes
docker system prune -af --volumes
# Memory
free -h
# Top 10 largest directories
du -sh /* 2>/dev/null | sort -rh | head -10
Troubleshooting
# Which process is using a port?
ss -tlnp | grep :3000
# Container restart loops
docker ps -a --filter "status=restarting" --format "{{.Names}}: {{.Status}}"
# Why did a container exit?
docker inspect --format='{{.State.ExitCode}}: {{.State.Error}}' <container-name>
# Docker Hub rate limit check
curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/alpine:pull" \
| jq -r '.token' \
| xargs -I{} curl -s -H "Authorization: Bearer {}" \
"https://registry-1.docker.io/v2/library/alpine/manifests/latest" -D - -o /dev/null 2>&1 \
| grep -i ratelimit