Deployment and Operations | XRPL APIs & Integration | XRP Academy - XRP Academy
3 free lessons remaining this month

Free preview access resets monthly

Upgrade for Unlimited
Skip to main content
advanced50 min

Deployment and Operations

Learning Objectives

Configure environments for development, staging, and production

Deploy XRPL integrations with zero-downtime strategies

Implement rollback procedures for failed deployments

Manage secrets and configuration across environments

Operate production systems with appropriate runbooks

// config/index.js
const config = {
  development: {
    xrpl: {
      servers: ['wss://s.altnet.rippletest.net:51233'],
      network: 'testnet'
    },
    wallets: {
      hot: { address: 'rTestHotWallet...' }
    },
    limits: {
      maxPayment: 10000,
      dailyLimit: 100000
    },
    features: {
      debugLogging: true,
      mockExternalServices: true
    }
  },

staging: {
xrpl: {
servers: [
'wss://s.altnet.rippletest.net:51233',
'wss://s.devnet.rippletest.net:51233'
],
network: 'testnet'
},
wallets: {
hot: { address: 'rStagingHotWallet...' }
},
limits: {
maxPayment: 10000,
dailyLimit: 100000
},
features: {
debugLogging: false,
mockExternalServices: false
}
},

production: {
xrpl: {
servers: [
'wss://s1.ripple.com:51233',
'wss://s2.ripple.com:51233',
'wss://xrplcluster.com:51233'
],
network: 'mainnet'
},
wallets: {
hot: { address: process.env.HOT_WALLET_ADDRESS }
},
limits: {
maxPayment: parseInt(process.env.MAX_PAYMENT) || 1000,
dailyLimit: parseInt(process.env.DAILY_LIMIT) || 10000
},
features: {
debugLogging: false,
mockExternalServices: false
}
}
}

const env = process.env.NODE_ENV || 'development'
module.exports = config[env]
```

class NetworkVerifier {
  constructor(client, expectedNetwork) {
    this.client = client
    this.expectedNetwork = expectedNetwork
  }

async verify() {
const serverInfo = await this.client.request({ command: 'server_info' })
const networkId = serverInfo.result.info.network_id

// Network IDs: 0 = mainnet, 1 = testnet, 2 = devnet
const networks = {
0: 'mainnet',
1: 'testnet',
2: 'devnet'
}

const actualNetwork = networks[networkId] || 'unknown'

if (actualNetwork !== this.expectedNetwork) {
throw new Error(
Network mismatch! Expected ${this.expectedNetwork}, connected to ${actualNetwork}
)
}

console.log(Network verified: ${actualNetwork})
return true
}
}

// Startup check
async function startupChecks(config) {
const client = new xrpl.Client(config.xrpl.servers[0])
await client.connect()

// Verify correct network
const verifier = new NetworkVerifier(client, config.xrpl.network)
await verifier.verify()

// Verify hot wallet exists and has balance
if (config.xrpl.network === 'mainnet') {
const info = await client.request({
command: 'account_info',
account: config.wallets.hot.address
})
const balance = parseInt(info.result.account_data.Balance) / 1_000_000
console.log(Hot wallet balance: ${balance} XRP)

if (balance < 100) {
console.warn('WARNING: Low hot wallet balance!')
}
}

await client.disconnect()
}
```


# docker-compose.yml
version: '3.8'

services:
xrpl-service-blue:
image: xrpl-service:${BLUE_VERSION}
deploy:
replicas: 2
environment:
- NODE_ENV=production
- SERVICE_COLOR=blue
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]" target="_blank" rel="noopener noreferrer" class="text-cyan-400 hover:text-cyan-300 underline hover:no-underline transition-colors inline-flex items-center gap-1">http://localhost:3000/health%22%5D">http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3

xrpl-service-green:
image: xrpl-service:${GREEN_VERSION}
deploy:
replicas: 2
environment:
- NODE_ENV=production
- SERVICE_COLOR=green
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]" target="_blank" rel="noopener noreferrer" class="text-cyan-400 hover:text-cyan-300 underline hover:no-underline transition-colors inline-flex items-center gap-1">http://localhost:3000/health%22%5D">http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3

nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- xrpl-service-blue
- xrpl-service-green
```

#!/bin/bash
# deploy.sh

set -e

NEW_VERSION=$1
CURRENT_COLOR=$(cat /var/run/current_color || echo "blue")
NEW_COLOR=$([ "$CURRENT_COLOR" = "blue" ] && echo "green" || echo "blue")

echo "Deploying version $NEW_VERSION to $NEW_COLOR..."

Deploy new version to inactive color

docker-compose up -d "xrpl-service-$NEW_COLOR"
-e "${NEW_COLOR^^}_VERSION=$NEW_VERSION"

Wait for health check

echo "Waiting for health check..."
for i in {1..30}; do
if curl -f "http://xrpl-service-$NEW_COLOR:3000/health" target="_blank" rel="noopener noreferrer" class="text-cyan-400 hover:text-cyan-300 underline hover:no-underline transition-colors inline-flex items-center gap-1">http://xrpl-service-$NEW_COLOR:3000/health">http://xrpl-service-$NEW_COLOR:3000/health " > /dev/null 2>&1; then
echo "New deployment healthy!"
break
fi
if [ $i -eq 30 ]; then
echo "Health check failed, rolling back..."
docker-compose stop "xrpl-service-$NEW_COLOR"
exit 1
fi
sleep 2
done

Run smoke tests

echo "Running smoke tests..."
./smoke-tests.sh "xrpl-service-$NEW_COLOR"
if [ $? -ne 0 ]; then
echo "Smoke tests failed, rolling back..."
docker-compose stop "xrpl-service-$NEW_COLOR"
exit 1
fi

Switch traffic

echo "Switching traffic to $NEW_COLOR..."
sed -i "s/xrpl-service-$CURRENT_COLOR/xrpl-service-$NEW_COLOR/g" /etc/nginx/nginx.conf
nginx -s reload

Update current color

echo "$NEW_COLOR" > /var/run/current_color

Stop old version after grace period

echo "Waiting for old connections to drain..."
sleep 30
docker-compose stop "xrpl-service-$CURRENT_COLOR"

echo "Deployment complete!"
```

app.get('/health', async (req, res) => {
  const health = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    version: process.env.APP_VERSION,
    checks: {}
  }

// Check XRPL connection
try {
if (xrplClient.isConnected()) {
await xrplClient.request({ command: 'ping' })
health.checks.xrpl = { status: 'ok' }
} else {
health.checks.xrpl = { status: 'disconnected' }
health.status = 'degraded'
}
} catch (error) {
health.checks.xrpl = { status: 'error', message: error.message }
health.status = 'unhealthy'
}

// Check database
try {
await db.query('SELECT 1')
health.checks.database = { status: 'ok' }
} catch (error) {
health.checks.database = { status: 'error', message: error.message }
health.status = 'unhealthy'
}

// Check Redis
try {
await redis.ping()
health.checks.redis = { status: 'ok' }
} catch (error) {
health.checks.redis = { status: 'error', message: error.message }
health.status = 'degraded'
}

const statusCode = health.status === 'healthy' ? 200 :
health.status === 'degraded' ? 200 : 503

res.status(statusCode).json(health)
})
```


class DeploymentManager {
  constructor(config) {
    this.config = config
    this.deploymentHistory = []
  }

async deploy(version) {
const deployment = {
version,
timestamp: Date.now(),
status: 'deploying'
}

this.deploymentHistory.push(deployment)

try {
await this.performDeployment(version)
await this.runHealthChecks()
await this.runSmokeTests()

deployment.status = 'success'
console.log(Deployment of ${version} successful)

} catch (error) {
deployment.status = 'failed'
deployment.error = error.message

console.error(Deployment failed: ${error.message})
await this.rollback()
throw error
}
}

async rollback() {
const successful = this.deploymentHistory
.filter(d => d.status === 'success')
.sort((a, b) => b.timestamp - a.timestamp)

if (successful.length === 0) {
throw new Error('No successful deployment to rollback to')
}

const rollbackTo = successful[0]
console.log(Rolling back to version ${rollbackTo.version})

await this.performDeployment(rollbackTo.version)
await this.runHealthChecks()

console.log('Rollback complete')
}

async runHealthChecks() {
const maxAttempts = 30
for (let i = 0; i < maxAttempts; i++) {
try {
const response = await fetch(${this.config.serviceUrl}/health)
const health = await response.json()

if (health.status === 'healthy') return true
if (health.status === 'unhealthy') throw new Error('Service unhealthy')

} catch (error) {
if (i === maxAttempts - 1) throw error
}
await sleep(2000)
}
throw new Error('Health check timeout')
}

async runSmokeTests() {
// Test basic operations
const tests = [
this.testXRPLConnection,
this.testDatabaseConnection,
this.testPaymentQuery
]

for (const test of tests) {
await test.call(this)
}
}
}
```


class SecretRotator {
  constructor(secretsManager, notifier) {
    this.secretsManager = secretsManager
    this.notifier = notifier
  }

async rotateApiKey(secretName) {
console.log(Rotating secret: ${secretName})

// Generate new key
const newKey = crypto.randomBytes(32).toString('hex')

// Update in secrets manager
await this.secretsManager.putSecretValue({
SecretId: secretName,
SecretString: JSON.stringify({
key: newKey,
rotatedAt: new Date().toISOString()
})
})

// Notify services to refresh
await this.notifier.broadcast('secret_rotated', { secretName })

console.log(Secret ${secretName} rotated successfully)
}

async getSecret(secretName) {
const result = await this.secretsManager.getSecretValue({
SecretId: secretName
})
return JSON.parse(result.SecretString)
}
}

// Application refreshes secrets periodically
class SecretCache {
constructor(secretsClient, refreshInterval = 300000) {
this.secretsClient = secretsClient
this.cache = new Map()
this.refreshInterval = refreshInterval

this.startRefreshLoop()
}

async get(secretName) {
if (!this.cache.has(secretName)) {
await this.refresh(secretName)
}
return this.cache.get(secretName)
}

async refresh(secretName) {
const secret = await this.secretsClient.getSecret(secretName)
this.cache.set(secretName, secret)
}

startRefreshLoop() {
setInterval(async () => {
for (const secretName of this.cache.keys()) {
try {
await this.refresh(secretName)
} catch (error) {
console.error(Failed to refresh secret ${secretName}:, error)
}
}
}, this.refreshInterval)
}
}
```


# Operational Runbooks


  • Configures environments (dev, staging, production)
  • Implements health checks
  • Supports blue-green deployment
  • Includes rollback capability
  • Manages secrets securely

Time Investment: 4-5 hours


End of Lesson 17

Key Takeaways

1

Verify network before operations:

Never accidentally operate on mainnet.

2

Deploy with rollback capability:

Blue-green enables instant rollback.

3

Health checks gate traffic:

Don't route to unhealthy instances.

4

Rotate secrets regularly:

Limit exposure from any single compromise.

5

Document procedures:

Runbooks enable consistent operations. ---