Skip to main content

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