Skip to main content
Version: 5.1

VM Deployment

This page explains how to run WSO2 Integrator projects as standalone JAR files on virtual machines for self-hosted production deployments.

Prerequisites
  • Ballerina installed on any machine where you want to run the project
  • A WSO2 Integrator project based on Ballerina
  • For production VM deployments: Java Runtime (JDK 25 or later)

Deploy with executable JAR​

For production VM deployments, WSO2 Integrator projects compile to executable JAR files that run on any JVM. This approach gives you full control over the runtime environment and is ideal for traditional VM-based infrastructure.

Prerequisites for JAR deployment​

RequirementDetails
Java RuntimeJDK 25 or later
Operating SystemLinux (recommended), macOS, or Windows
MemoryMinimum 512 MB, recommended 1 GB+ per instance
BallerinaDistribution installed on the build machine

Build the executable JAR​

Use the bal build command to produce a standalone executable JAR.

bal build

This generates a fat JAR in the target/bin/ directory:

target/
bin/
my_integration.jar

Run it directly:

java -jar target/bin/my_integration.jar

Build options​

FlagDescription
--cloud=dockerAlso generate Docker artifacts
--graalvmBuild a GraalVM native image
--observability-includedBundle observability dependencies

Transfer the JAR to your VM​

scp target/bin/my_integration.jar user@production-vm:/opt/integrations/

Configure the runtime​

Create a Config.toml in the same directory as the JAR (or set the BAL_CONFIG_FILES environment variable):

[myIntegration.http]
port = 9090

[myIntegration.db]
host = "db.internal.example.com"
port = 5432
username = "svc_user"
password = "encrypted:xxxxx"

Start the integration​

java -jar my_integration.jar

For production deployments with JVM tuning:

java \
-Xms512m \
-Xmx1024m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/integrations/ \
-jar my_integration.jar

Verify the deployment​

curl http://localhost:9090/health

Run on a VM from source​

To run the project on a remote virtual machine, install the Ballerina distribution on the VM and run the project directly from source using bal run.

Step 1: Push the project to a remote Git repository​

From your local machine, push the project to a remote Git repository such as GitHub, GitLab, or Bitbucket.

git add .
git commit -m "Initial integration project"
git push origin main

Step 2: Clone the repository on the VM​

SSH into the target VM and clone the repository.

git clone https://github.com/your-org/my-integration.git
cd my-integration

Step 3: Start the integration​

Run the project using the Ballerina CLI.

bal run

The runtime starts and the integration begins serving traffic on the configured port.

note

Ballerina must be installed on the VM. Download it from ballerina.io. The version on the VM should match the version used during development.

Consolidated package deployment​

For organizations running multiple integrations, a consolidated deployment bundles several integration packages into a single runtime.

Creating a consolidated package​

  1. Create a consolidation project:
bal new consolidated_deploy -t lib
  1. Add each integration as a dependency in Ballerina.toml:
[package]
org = "myorg"
name = "consolidated_deploy"
version = "1.0.0"

[[dependency]]
org = "myorg"
name = "order_service"
version = "1.2.0"

[[dependency]]
org = "myorg"
name = "inventory_sync"
version = "1.0.3"

[[dependency]]
org = "myorg"
name = "notification_handler"
version = "2.1.0"
  1. Import each integration in a source file of the consolidation project (for example, main.bal) so that its services start with the runtime. A dependency entry in Ballerina.toml alone does not start them:
import myorg/order_service as _;
import myorg/inventory_sync as _;
import myorg/notification_handler as _;
  1. Build the consolidated JAR:
bal build

Running the consolidated package​

java -jar target/bin/consolidated_deploy.jar

All services start within the same JVM process, sharing resources.

Consolidated Config.toml​

Provide configuration for all included integrations in a single file:

# Order Service configuration
[order_service.http]
port = 9091

[order_service.db]
host = "db.internal.example.com"

# Inventory Sync configuration
[inventory_sync.schedule]
cronExpression = "0 */5 * * * ?"

[inventory_sync.endpoint]
url = "https://erp.example.com/api"

# Notification Handler configuration
[notification_handler.email]
smtpHost = "smtp.example.com"
smtpPort = 587

JVM tuning for production​

Recommended JVM flags for production deployments:

java \
-Xms512m \
-Xmx1024m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/integrations/ \
-jar my_integration.jar
JVM FlagPurpose
-Xms / -XmxInitial and maximum heap size
-XX:+UseG1GCUse the G1 garbage collector (recommended)
-XX:MaxGCPauseMillisTarget max GC pause time
-XX:+HeapDumpOnOutOfMemoryErrorGenerate heap dump on OOM

Log management​

Configure logging in Config.toml:

[ballerina.log]
level = "INFO"

[[ballerina.log.destinations]]
path = "./logs/app.log"

Health checks and monitoring​

Expose a health endpoint for load balancers and monitoring systems:

import ballerina/http;

service /health on new http:Listener(9091) {
resource function get .() returns http:Ok {
return http:OK;
}
}

Run on Amazon EC2​

If the integration uses AWS connectors, don't copy access keys to the instance. Attach an instance profile that contains an IAM role with the permissions the connectors need, and configure the connectors with auth:DEFAULT_CREDENTIALS:

aws ec2 associate-iam-instance-profile \
--instance-id <instance-id> \
--iam-instance-profile Name=my-integration-profile

The connectors then read temporary credentials from the instance metadata service and refresh them automatically. See Access AWS Services Securely.

note

If the integration runs in a container on the instance and IMDSv2 is enforced, set the metadata hop limit to 2. Otherwise the container cannot reach the metadata service:

aws ec2 modify-instance-metadata-options \
--instance-id <instance-id> \
--http-put-response-hop-limit 2 \
--http-tokens required
warning

The instance profile is shared by everything on the instance. With the hop limit at 2, any container that can reach the metadata service, not just the integration, can get the role's credentials. Run the integration on an instance dedicated to it. If other containers must share the instance, run them on Amazon ECS with a separate task role for each, and block their access to the metadata service. For strict isolation, use AWS Fargate. See the Amazon EC2 instance configuration for ECS task roles in the AWS documentation.

What's next​