Joblet is a micro-container runtime for running Linux jobs with: Process and filesystem isolation (PID namespace, chroot) Fine-grained CPU, memory, and IO throttling (cgroups v2) Secure job execution with mTLS and RBAC Built-in scheduler, SSE log streaming, and multi-core pinning Ideal for: Agentic AI Workloads (Untrusted code)
Deploy Joblet on AWS EC2 in 2 simple steps (~10 minutes total).
Open AWS Console → CloudShell (top-right toolbar icon) and run ONE of the following based on your storage preference:
This interactive script creates:
JobletEC2Role IAM role with permissions based on your storage selection:
joblet-jobs DynamoDB table for job state persistenceThe script will:
Note the VPC ID shown at the end - you’ll need it in Step 2.
Go to EC2 Console → Launch Instance
joblet-servert3.medium (or larger)JobletEC2Role ⬅️ Created in Step 1Expand “Advanced Details” → Scroll to “User data” and paste ONE of the following scripts based on your storage preference:
See Storage Backend Options for detailed configuration options.
When the instance boots, the user data script automatically:
/joblet log group for real-time aggregation/opt/joblet/logs/ on diskNote: DynamoDB table is created in Step 1 (pre-setup), not on EC2 instance.
Total time: ~5 minutes after launch
After the instance is running (wait ~5 minutes for installation):
# Get the public IP from EC2 Console
PUBLIC_IP="x.x.x.x" # Replace with actual IP
# Download client config
mkdir -p ~/.rnx
scp -i ~/.ssh/your-key.pem ubuntu@${PUBLIC_IP}:/opt/joblet/config/rnx-config.yml ~/.rnx/
# List jobs (should return empty list)
rnx job list
# Run first job
rnx job run echo "Hello from Joblet on AWS!"
# View job logs (fetched from configured storage backend)
rnx job log <job-id>
# Check job status
rnx job status <job-id>
# SSH to instance
ssh -i ~/.ssh/your-key.pem ubuntu@${PUBLIC_IP}
# Check Joblet service status
sudo systemctl status joblet
# View DynamoDB table (job state)
aws dynamodb describe-table --table-name joblet-jobs
Verify storage backend (based on your selection):
# If using CloudWatch:
aws logs describe-log-streams --log-group-name /joblet
# If using S3:
aws s3 ls s3://your-bucket-name/joblet/
# If using Local:
ssh ubuntu@${PUBLIC_IP} "ls -la /opt/joblet/logs/"
JobletEC2Role)/joblet log group) - Default Backend/joblet/{nodeId}/jobs/{job_uuid}{prefix}/{nodeId}/{jobId}/{type}/{timestamp}.jsonl.gzPERSIST_BACKEND=s3 S3_BUCKET=your-bucketjoblet-jobs table)When using S3 storage backend, the pre-setup script also configures:
pre-setup.sh --storage=s3Note: If the S3 VPC Endpoint policy doesn’t allow access to your bucket, you’ll see AccessDenied errors in the persist service logs. The pre-setup script updates the endpoint policy automatically to allow access to the specified bucket.
joblet/ca-cert, joblet/ca-key) - Same across all instancesjoblet/client-cert, joblet/client-key) - Same for all clientsJoblet is designed to always start, even if AWS services are unavailable:
| Service | Primary | Fallback | Behavior |
|---|---|---|---|
| State | DynamoDB | In-memory | Jobs work, but state lost on restart |
| Persist | CloudWatch / S3 | Local disk | Logs stored at /opt/joblet/logs |
When running in fallback mode, Joblet logs prominent warnings so you know AWS integration is not working. This ensures Joblet remains functional for development/testing even without proper AWS setup.
Joblet supports three storage backends for logs and metrics. Select your preferred backend in Step 2 when configuring User Data.
| Backend | Best For | Pros | Cons |
|---|---|---|---|
| CloudWatch (default) | Real-time monitoring, AWS integration | Native AWS integration, real-time queries, CloudWatch Insights | Higher cost for high-volume logs |
| S3 | Long-term archival, cost optimization | Very low cost, unlimited storage, lifecycle policies | Not real-time, requires S3 bucket setup |
| Local | Development, testing, VMs | No AWS dependencies, zero cost | Lost on instance termination, no aggregation |
The install script supports command-line options for cleaner User Data:
/tmp/joblet-install.sh [OPTIONS]
Options:
--storage=TYPE cloudwatch (default), s3, or local
--s3-bucket=NAME S3 bucket name (required for s3)
--s3-prefix=PREFIX S3 key prefix (default: jobs/)
--s3-class=CLASS STANDARD, STANDARD_IA, GLACIER, etc.
--version=VERSION Joblet version (default: latest)
--port=PORT Server port (default: 443)
--help Show all options
No additional configuration needed. CloudWatch log group /joblet is created automatically.
/tmp/joblet-install.sh --storage=cloudwatch
Environment variables (alternative):
| Variable | Default | Description |
|---|---|---|
PERSIST_BACKEND |
cloudwatch |
Explicitly set backend |
CLOUDWATCH_LOG_GROUP |
/joblet |
Custom log group name |
CLOUDWATCH_RETENTION_DAYS |
7 |
Log retention period |
/tmp/joblet-install.sh --storage=s3 --s3-bucket=my-bucket --s3-prefix=joblet --s3-class=STANDARD
Environment variables (alternative):
| Variable | Required | Default | Description |
|---|---|---|---|
PERSIST_BACKEND |
Yes | - | Must be s3 |
S3_BUCKET |
Yes | - | Your S3 bucket name |
S3_PREFIX |
No | joblet |
Key prefix for all objects |
S3_STORAGE_CLASS |
No | STANDARD |
Storage class (see below) |
S3 Storage Classes:
STANDARD - Frequently accessed (default)STANDARD_IA - Infrequent access, lower costONEZONE_IA - Single AZ, cheapest for non-critical dataGLACIER - Archive, minutes to hours retrievalDEEP_ARCHIVE - Long-term archive, 12+ hours retrievalS3 IAM Permissions:
When you run pre-setup.sh --storage=s3 --s3-bucket=YOUR_BUCKET, the IAM policy is automatically configured with these permissions scoped to your bucket:
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::YOUR_BUCKET",
"arn:aws:s3:::YOUR_BUCKET/*"
]
}
Note: If you ran
pre-setup.shwithout--storage=s3, you’ll need to manually add S3 permissions toJobletEC2Roleor re-run the pre-setup with the correct storage option.
S3 Lifecycle Rules (Recommended):
Configure lifecycle rules on your S3 bucket for automatic cost optimization:
aws s3api put-bucket-lifecycle-configuration --bucket my-joblet-logs \
--lifecycle-configuration '{
"Rules": [{
"ID": "JobletArchiveRule",
"Status": "Enabled",
"Filter": {"Prefix": "joblet/"},
"Transitions": [{"Days": 30, "StorageClass": "GLACIER"}],
"Expiration": {"Days": 365}
}]
}'
For development, testing, or VM deployments without AWS. No S3 bucket or CloudWatch permissions needed.
/tmp/joblet-install.sh --storage=local
Local Storage Behavior:
/opt/joblet/logs//opt/joblet/metrics/Environment variables (alternative):
| Variable | Default | Description |
|---|---|---|
PERSIST_BACKEND |
- | Must be local |
LOCAL_LOG_DIR |
/opt/joblet/logs |
Custom log directory |
LOCAL_METRICS_DIR |
/opt/joblet/metrics |
Custom metrics directory |
If you prefer command-line automation instead of the Console:
# Step 1: AWS Pre-Setup (IAM + DynamoDB)
# For CloudWatch (default):
curl -fsSL https://raw.githubusercontent.com/ehsaniara/joblet/main/scripts/aws/pre-setup.sh | bash -s -- --storage=cloudwatch
# For S3 (interactive - will prompt for bucket name):
curl -fsSL https://raw.githubusercontent.com/ehsaniara/joblet/main/scripts/aws/pre-setup.sh | bash -s -- --storage=s3
# For S3 (non-interactive - for scripts/automation):
curl -fsSL https://raw.githubusercontent.com/ehsaniara/joblet/main/scripts/aws/pre-setup.sh | bash -s -- --storage=s3 --s3-bucket=my-bucket
# Step 2: Launch instance (will prompt for security group)
export KEY_NAME="your-ssh-key-name"
curl -fsSL https://raw.githubusercontent.com/ehsaniara/joblet/main/scripts/aws/launch-instance.sh | bash
The launch-instance.sh script will:
To deploy without CloudWatch or DynamoDB:
Note:
ENABLE_CLOUDWATCH=falseis still supported for backward compatibility, butPERSIST_BACKEND=localis preferred.
This results in:
/opt/joblet/logs/SSH to the instance and watch the installation:
ssh -i ~/.ssh/your-key.pem ubuntu@${PUBLIC_IP}
# Watch installation log
tail -f /var/log/joblet-install.log
# Check if Joblet is running
sudo systemctl status joblet
# View Joblet logs
sudo journalctl -u joblet -f
Required Rules:
Optional Rules:
0.0.0.0/0 (if you want to allow connections from anywhere - not recommended for production)Important: Always restrict SSH and gRPC access to your IP or VPC CIDR range for security.
If your EC2 instance is in a private subnet without public IP:
# Create SSH tunnel through bastion (from your workstation)
# Replace <PRIVATE_IP> with the Joblet EC2 instance's private IP (e.g., 172.31.16.50)
ssh -i ~/.ssh/your-key.pem -L 50051:<PRIVATE_IP>:443 ubuntu@<BASTION_IP>
# Configure client to use localhost
# Edit ~/.rnx/rnx-config.yml:
# address: localhost:50051
If you see warnings like these in the logs (sudo journalctl -u joblet):
========================================================================
[STATE] WARNING: Running with IN-MEMORY backend (fallback mode)
[STATE] Job state will NOT persist across restarts!
[STATE] Reason: failed to connect to dynamodb
[STATE] To fix: Check VPC Endpoint, IAM role, and DynamoDB table
========================================================================
Or:
========================================================================
[PERSIST] WARNING: Running with LOCAL storage backend (fallback mode)
[PERSIST] Logs will be stored on disk at /opt/joblet/logs
[PERSIST] CloudWatch Logs integration is DISABLED
[PERSIST] Reason: failed to connect to cloudwatch
[PERSIST] To fix: Check IAM role and CloudWatch permissions
========================================================================
This means Joblet is running but with reduced functionality:
| Service | Normal Mode | Fallback Mode | Impact |
|---|---|---|---|
| State | DynamoDB | In-memory | Job state lost on restart |
| Persist | CloudWatch | Local disk | Logs only on EC2, not in CloudWatch |
Common causes and fixes:
JobletEC2Role to EC2 instanceJobletEC2Role policyJobletEC2Role to EC2 instanceTo verify and fix:
# Check if IAM role is attached (using IMDSv2)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Check VPC Endpoint exists (from CloudShell or local AWS CLI)
aws ec2 describe-vpc-endpoints --filters "Name=service-name,Values=com.amazonaws.REGION.dynamodb"
# Check DynamoDB table exists
aws dynamodb describe-table --table-name joblet-jobs --region REGION
# Restart Joblet after fixing
sudo systemctl restart joblet
Check installation log:
ssh -i ~/.ssh/your-key.pem ubuntu@${PUBLIC_IP}
cat /var/log/joblet-install.log
Common issues:
# Check service status
sudo systemctl status joblet
# View logs
sudo journalctl -u joblet -n 50
# Verify configuration
cat /opt/joblet/config/joblet-config.yml
# Check if IAM role has permissions
aws iam get-role --role-name JobletEC2Role
aws iam list-attached-role-policies --role-name JobletEC2Role
# Manually create table (if needed)
aws dynamodb create-table \
--table-name joblet-jobs \
--attribute-definitions AttributeName=job_uuid,AttributeType=S \
--key-schema AttributeName=job_uuid,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region us-east-1
# Check IAM permissions
aws iam get-role-policy --role-name JobletEC2Role --policy-name JobletAWSPolicy
# Verify CloudWatch agent/config
cat /opt/joblet/config/joblet-config.yml | grep -A 5 "persist:"
# Check log group exists
aws logs describe-log-groups --log-group-name-prefix /joblet
If you see errors like:
AccessDenied: User is not authorized to perform: s3:PutObject on resource
because no VPC endpoint policy allows the s3:PutObject action
Cause: The S3 VPC Endpoint policy doesn’t allow access to your bucket.
Fix 1: Re-run pre-setup with your bucket name:
./pre-setup.sh --storage=s3 --s3-bucket=your-bucket-name
Fix 2: Manually update the S3 VPC Endpoint policy:
# Find the S3 VPC Endpoint ID
aws ec2 describe-vpc-endpoints --filters "Name=service-name,Values=com.amazonaws.REGION.s3" \
--query 'VpcEndpoints[*].[VpcEndpointId,VpcId]' --output table
# Update the policy
aws ec2 modify-vpc-endpoint --vpc-endpoint-id vpce-xxxx \
--policy-document '{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Principal":"*",
"Action":"s3:*",
"Resource":["arn:aws:s3:::your-bucket","arn:aws:s3:::your-bucket/*"]
}]
}'
Fix 3: Via AWS Console:
Check security group:
# From EC2 Console or CLI
aws ec2 describe-security-groups --group-ids sg-xxxxx
Verify port 443 is open from your IP
Test connectivity:
# From your workstation
telnet ${PUBLIC_IP} 443
# Or with openssl
openssl s_client -connect ${PUBLIC_IP}:443
Verify Joblet is listening:
ssh -i ~/.ssh/your-key.pem ubuntu@${PUBLIC_IP}
sudo netstat -tulpn | grep 443
Check if certificates were generated:
cat /opt/joblet/config/joblet-config.yml | grep -A 20 "certificates:"
┌───────────────────────────────────────────────────────────────────┐
│ AWS Account │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ VPC │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ EC2 Instance (Ubuntu 24.04 LTS) │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────────────────────────────────┐ │ │ │
│ │ │ │ Joblet Server (port 443) │ │ │ │
│ │ │ │ - Job execution │ │ │ │
│ │ │ │ - gRPC API │ │ │ │
│ │ │ │ - TLS certificates (embedded) │ │ │ │
│ │ │ └──────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │ │
│ │ └────────────────────┼───────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ │ │
│ │ ┌───────────┴───────────┐ │ │
│ │ ▼ ▼ │ │
│ │ ┌───────────────────┐ ┌───────────────────┐ │ │
│ │ │ VPC Endpoint │ │ VPC Endpoint │ │ │
│ │ │ (DynamoDB Gateway)│ │ (S3 Gateway)* │ │ │
│ │ └─────────┬─────────┘ └─────────┬─────────┘ │ │
│ │ │ │ │ │
│ └────────────┼──────────────────────┼─────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌─────────────────┐ │
│ │ DynamoDB │ │ CloudWatch Logs │ │
│ │ │ │ OR S3 Bucket │ │
│ │ Table: joblet-jobs │ │ /joblet/... │ │
│ │ (job state) │ │ (job logs) │ │
│ └──────────────────────┘ └─────────────────┘ │
│ │
│ * S3 VPC Endpoint created when using --storage=s3 │
│ │
└───────────────────────────────────────────────────────────────────┘
↑
│ gRPC (port 443)
│
┌──────────────┐
│ rnx Client │
│ (your laptop)│
└──────────────┘
rnx job log