Deployment¶
Deploy OmniAgent-based applications to cloud infrastructure.
Overview¶
OmniAgent applications can be deployed as:
- Standalone binary - Direct deployment to VMs
- Container - Docker deployment to container services
- Managed service - Using OmniDeploy for automated infrastructure
This guide covers containerized deployment using OmniDeploy for a single-operator (personal-mode) deployment. For a self-hosted team-mode stack (Caddy + PostgreSQL via Docker Compose on one VM), see Team Deployment instead.
Deployment Checklist¶
Follow these steps to deploy your OmniAgent application to AWS LightSail.
Prerequisites¶
Before starting, ensure you have:
- [ ] Go 1.26+ installed
- [ ] Docker installed and running
- [ ] AWS CLI configured
- [ ] GitHub account with a Personal Access Token (PAT) for GHCR
Step 1: Prepare Deployment Files¶
Ensure your project has these files:
| File | Purpose |
|---|---|
Dockerfile |
Multi-stage container build |
deploy.yaml |
OmniDeploy configuration |
.github/workflows/build.yaml |
CI container build (optional) |
Step 2: Commit and Push to GitHub¶
# Add deployment files
git add Dockerfile deploy.yaml .github/
# Commit
git commit -m "build: add Docker and OmniDeploy deployment configuration"
# Push to trigger CI build
git push origin main
Step 3: Build Container Image¶
Option A: Via GitHub Actions (recommended)
Pushing a version tag (vX.Y.Z) triggers .github/workflows/docker.yaml
automatically (RMI-OMNIAGENT-003), publishing
ghcr.io/<owner>/<repo>:vX.Y.Z, :X.Y, and :latest. Trigger a one-off
build without a tag via Actions → Docker Build & Publish → Run
workflow, which publishes a :smoke tag instead.
Option B: Build locally
# Start Docker if not running
open -a Docker # macOS
# Build for linux/amd64 (required for Lightsail)
docker build --platform linux/amd64 -t ghcr.io/<owner>/<repo>:latest .
# Login to GHCR (see GHCR Authentication below)
echo $GITHUB_TOKEN | docker login ghcr.io -u <username> --password-stdin
# Push
docker push ghcr.io/<owner>/<repo>:latest
GHCR Authentication¶
GitHub Container Registry (GHCR) uses a standard GitHub Personal Access Token (PAT).
Create a token:
- Go to https://github.com/settings/tokens
- Click Generate new token (classic)
- Select scopes:
write:packages,read:packages,delete:packages - Copy the token (shown once)
Login:
# Store token in environment (don't commit)
export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Login to GHCR
echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin
Make the package public (one-time, after first push):
- Go to https://github.com/orgs/
/packages (or your user packages page) - Find the package and open Package settings
- Under Danger Zone, click Change visibility → Public
Once public, Lightsail can pull the image without registry credentials, which sidesteps the need for PrivateRegistryAccess configuration.
Step 4: Install OmniDeploy¶
Verify installation:
Step 5: Configure AWS Credentials¶
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_REGION="us-east-1"
Or use AWS CLI profiles:
Step 6: Store Secrets in AWS SSM (Recommended)¶
Store API keys securely instead of in environment variables:
# Anthropic API key
aws ssm put-parameter \
--name "/<app-name>/anthropic-api-key" \
--value "sk-ant-..." \
--type SecureString
# Other API keys
aws ssm put-parameter \
--name "/<app-name>/other-api-key" \
--value "..." \
--type SecureString
Update deploy.yaml to reference secrets:
Step 7: Preview Deployment¶
Review the resources that will be created.
Step 8: Deploy¶
The deployment outputs the service URL.
Step 9: Verify Deployment¶
# Health check
curl https://<service-url>/health
# Test chat endpoint
curl https://<service-url>/openai/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"default","messages":[{"role":"user","content":"Hello"}]}'
Step 10: Monitor and Maintain¶
# View logs (via AWS Console or CLI)
aws lightsail get-container-log \
--service-name <app-name> \
--container-name <app-name>
# Update deployment (after pushing new image)
omnideploy up --config deploy.yaml --target lightsail --backend pulumi --yes
# Destroy when done
omnideploy destroy --stack <app-name> --yes
Building a Custom Agent¶
Before deployment, you typically create a custom agent that bundles compiled skills with OmniAgent.
Storage no longer requires a custom binary
The STORAGE_PATH-driven RegisterAgentOption(agent.WithStorage(...))
step below predates RMI-OMNIAGENT-007: omniagent gateway run now
builds a durable storage backend itself from storage.type/
storage.path/storage.redis.url config (see
Configuration → Storage), and
starts the cron scheduler against it in single-agent mode. A custom
binary is still the right call for bundling compiled skills, but
if that's all you needed a custom main.go for, the stock binary
plus a storage: block in your deployment config now covers it.
Project Structure¶
my-agent/
├── cmd/
│ └── agent/
│ └── main.go # Entry point with compiled skills
├── config/
│ └── config.yaml # Default configuration
├── Dockerfile # Container build
├── deploy.yaml # OmniDeploy configuration
├── go.mod
└── go.sum
Entry Point¶
Create cmd/agent/main.go:
package main
import (
"log"
"os"
"github.com/plexusone/omniagent/agent"
"github.com/plexusone/omniagent/cmd/omniagent/commands"
"github.com/plexusone/omnistorage/kvs/sqlite"
// Import your compiled skills
myskill "github.com/example/my-skill/omniagent/skill"
)
func main() {
// Configure storage
storagePath := os.Getenv("STORAGE_PATH")
if storagePath != "" {
store, err := sqlite.New(sqlite.Config{Path: storagePath})
if err != nil {
log.Fatalf("failed to create storage: %v", err)
}
commands.RegisterAgentOption(agent.WithStorage(store))
}
// Register compiled skills
skill, err := myskill.New(myskill.Config{
APIKey: os.Getenv("MY_SKILL_API_KEY"),
})
if err != nil {
log.Fatalf("failed to create skill: %v", err)
}
commands.RegisterAgentOption(agent.WithCompiledSkill(skill))
// Run the standard OmniAgent CLI
if err := commands.Execute(); err != nil {
log.Fatalf("error: %v", err)
}
}
Configuration¶
Create config/config.yaml:
agent:
name: my-agent
greeting: "Hello, I'm My Agent. How can I help?"
llm:
provider: anthropic
model: claude-sonnet-5
gateway:
enabled: true
addr: ":8080"
skills:
enabled: true
Containerization¶
Dockerfile¶
Create a multi-stage Dockerfile for minimal image size:
# Build stage
FROM golang:1.26-alpine AS builder
WORKDIR /app
# Install git for private dependencies
RUN apk add --no-cache git ca-certificates
# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download
# Build binary
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w" \
-o /app/my-agent \
./cmd/agent
# Runtime stage
FROM alpine:3.20
RUN apk add --no-cache ca-certificates tzdata
# Non-root user
RUN adduser -D -u 1000 omniagent
RUN mkdir -p /data /opt/omniagent && chown -R omniagent:omniagent /data /opt/omniagent
WORKDIR /opt/omniagent
# Copy binary and config
COPY --from=builder /app/my-agent /opt/omniagent/my-agent
COPY --from=builder /app/config/config.yaml /opt/omniagent/config.yaml
USER omniagent
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -q --spider http://localhost:8080/health || exit 1
ENV OMNIAGENT_GATEWAY_ADDRESS="0.0.0.0:8080" \
STORAGE_PATH="/data/omniagent.db"
ENTRYPOINT ["/opt/omniagent/my-agent"]
CMD ["gateway", "run"]
Build Locally¶
# Build image
docker build -t my-agent:latest .
# Test locally
docker run -p 8080:8080 \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
my-agent:latest
OmniDeploy Configuration¶
OmniDeploy provides declarative deployment to multiple cloud targets.
Installation¶
Configuration File¶
Create deploy.yaml:
name: my-agent
version: "1.0.0"
region: us-east-1
container:
image: ghcr.io/example/my-agent:latest
ports:
- container_port: 8080
protocol: HTTP
name: api
health_check:
path: /health
interval: 30s
timeout: 5s
healthy_threshold: 2
unhealthy_threshold: 3
service:
replicas: 1
public: true
resources:
# LightSail sizes: nano, micro, small, medium, large, xlarge
size: small
environment:
OMNIAGENT_GATEWAY_ADDRESS: "0.0.0.0:8080"
STORAGE_PATH: "/data/omniagent.db"
OMNIAGENT_AGENT_PROVIDER: "anthropic"
OMNIAGENT_AGENT_MODEL: "claude-sonnet-5"
tags:
app: my-agent
environment: production
managed-by: omnideploy
Secrets Management¶
For sensitive values like API keys, use AWS Secrets Manager:
secrets:
- name: ANTHROPIC_API_KEY
source: ssm:/my-agent/anthropic-api-key
- name: MY_SKILL_API_KEY
source: ssm:/my-agent/skill-api-key
Store secrets in AWS:
aws ssm put-parameter \
--name "/my-agent/anthropic-api-key" \
--value "sk-ant-..." \
--type SecureString
Deploying to AWS LightSail¶
Prerequisites¶
- AWS credentials configured
- Container image pushed to registry
- OmniDeploy installed
Deploy¶
# Set AWS credentials
export AWS_ACCESS_KEY_ID="your-key"
export AWS_SECRET_ACCESS_KEY="your-secret"
export AWS_REGION="us-east-1"
# Preview deployment
omnideploy preview --config deploy.yaml --target lightsail --backend pulumi
# Deploy
omnideploy up --config deploy.yaml --target lightsail --backend pulumi --yes
Verify¶
# Get service URL from deployment output
curl https://my-agent.xxxxx.us-east-1.cs.amazonlightsail.com/health
# Test chat endpoint
curl https://my-agent.xxxxx.us-east-1.cs.amazonlightsail.com/openai/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"my-agent","messages":[{"role":"user","content":"Hello"}]}'
Destroy¶
CI/CD with GitHub Actions¶
Container Build Workflow¶
Create .github/workflows/build.yaml:
name: Build and Push Container
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Deployment Workflow¶
Create .github/workflows/deploy.yaml:
name: Deploy to LightSail
on:
workflow_run:
workflows: ["Build and Push Container"]
types: [completed]
branches: [main]
jobs:
deploy:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.26'
- name: Install OmniDeploy
run: go install github.com/plexusone/omnideploy/cmd/omnideploy@latest
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: us-east-1
run: |
omnideploy up \
--config deploy.yaml \
--target lightsail \
--backend pulumi \
--yes
- name: Health Check
run: |
for i in {1..30}; do
if curl -sf "${{ vars.SERVICE_URL }}/health"; then
echo "Deployment healthy"
exit 0
fi
sleep 10
done
echo "Health check failed"
exit 1
Required Secrets¶
Configure in Settings → Secrets and variables → Actions:
| Secret | Description |
|---|---|
AWS_ACCESS_KEY_ID |
AWS access key |
AWS_SECRET_ACCESS_KEY |
AWS secret key |
Configure in Settings → Secrets and variables → Actions → Variables:
| Variable | Description |
|---|---|
SERVICE_URL |
Deployed service URL for health checks |
Deploy Script¶
For manual deployments, create deploy/deploy.sh:
#!/bin/bash
# Deploy to AWS LightSail using OmniDeploy
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG_FILE="$PROJECT_ROOT/deploy.yaml"
STACK_NAME="${STACK_NAME:-my-agent}"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() { echo -e "${GREEN}[deploy]${NC} $1"; }
warn() { echo -e "${YELLOW}[warn]${NC} $1"; }
# Check prerequisites
check_prereqs() {
command -v omnideploy &> /dev/null || {
echo "Install omnideploy: go install github.com/plexusone/omnideploy/cmd/omnideploy@latest"
exit 1
}
[ -z "$AWS_ACCESS_KEY_ID" ] && warn "AWS_ACCESS_KEY_ID not set"
[ ! -f "$CONFIG_FILE" ] && { echo "Config not found: $CONFIG_FILE"; exit 1; }
}
# Build and push image
build() {
log "Building container image..."
cd "$PROJECT_ROOT"
VERSION=$(git describe --tags --always 2>/dev/null || echo "latest")
IMAGE="ghcr.io/example/my-agent:$VERSION"
docker build -t "$IMAGE" -t "ghcr.io/example/my-agent:latest" .
docker push "$IMAGE"
docker push "ghcr.io/example/my-agent:latest"
log "Image pushed: $IMAGE"
}
# Deploy
deploy() {
log "Deploying to LightSail..."
omnideploy up \
--config "$CONFIG_FILE" \
--target lightsail \
--backend pulumi \
--stack "$STACK_NAME" \
--yes
}
# Preview
preview() {
log "Previewing deployment..."
omnideploy preview \
--config "$CONFIG_FILE" \
--target lightsail \
--backend pulumi
}
# Destroy
destroy() {
warn "This will destroy all resources in stack: $STACK_NAME"
read -p "Are you sure? (y/N) " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] && omnideploy destroy --stack "$STACK_NAME" --yes
}
check_prereqs
case "${1:-deploy}" in
build) build ;;
preview) preview ;;
deploy) deploy ;;
destroy) destroy ;;
full) build && deploy ;;
*)
echo "Usage: $0 {build|preview|deploy|destroy|full}"
exit 1
;;
esac
Resource Sizing¶
LightSail Container Sizes¶
| Size | vCPU | Memory | Monthly Cost |
|---|---|---|---|
| nano | 0.25 | 512MB | ~$7 |
| micro | 0.5 | 1GB | ~$10 |
| small | 1 | 2GB | ~$25 |
| medium | 2 | 4GB | ~$50 |
| large | 4 | 8GB | ~$100 |
| xlarge | 8 | 16GB | ~$200 |
Recommendations¶
| Use Case | Size | Notes |
|---|---|---|
| Development/Testing | nano | Minimal traffic |
| Personal assistant | micro | Light usage |
| Production API | small | Moderate traffic |
| High-traffic API | medium+ | Scale replicas |
Other Deployment Targets¶
OmniDeploy supports additional targets (planned):
| Target | Status | Description |
|---|---|---|
| lightsail | Available | AWS LightSail containers |
| ecs | Planned | AWS ECS/Fargate |
| kubernetes | Planned | Kubernetes clusters |
| digitalocean | Planned | DigitalOcean App Platform |
See OmniDeploy documentation for details.