migrations.md

Migrate to Maritime

Move the automation layer of your cloud onto Maritime agents: cron jobs, scheduled functions, background workers, and bots. Each guide below inventories a provider with read-only credentials, recreates every workload as a Maritime agent with a schedule and encrypted env vars, verifies it end to end, and only then hands you a reversible cutover checklist. An agent runs in an agent box, which costs $1 a month per 2 GB of memory, awake or asleep (see Billing & plans), so a cron agent costs the same whether it runs for one second a day or all day.

Note: The safety model, in one box. Inventory runs on read-only credentials you mint for the occasion. Credentials travel through env vars, never chat. Nothing on the source provider is modified or deleted by an agent, ever: cutover is a checklist you run yourself, and every checklist starts with a reversible step (disable, pause, scale to zero) before any delete. Both sides run in parallel until you are satisfied.

Provider guides:

How every migration works

All ten guides follow the same five steps. They are written to be followed by a coding agent (point Claude Code or your Maritime agent at one) or by a human in a terminal. Each summary below links to the complete step-by-step runbook for that provider.

  1. Inventory: sweep the provider with its own CLI and read-only credentials, and list everything agent-shaped: things that wake up, do a job, and go quiet.
  2. Map: schedule expressions become cron triggers, env vars and secrets become encrypted agent env vars, logs become maritime logs.
  3. Recreate: an LLM-driven bot lands on a framework template; arbitrary code lands as a bring-your-own container from your repo. Porting a serverless function into a container is real work, and the guides say so instead of pretending.
  4. Verify: the new agent must do the job at least once, checked against a known-good run, while the old side is still untouched.
  5. Cut over: disable the old schedule (reversible), watch for a few days, then delete, in that order, with your own credentials.
npm i -g maritime-cli && maritime login
maritime keys create --name migration --json   # mk_... shown once
export MARITIME_TOKEN=mk_...

maritime templates --json                      # pick a template id from the live list
maritime create nightly-report --template <id> --json
maritime env import nightly-report ./old-provider.env --reload --json
maritime chat nightly-report "run your task once now and report" --json

Schedules are recreated as cron triggers, on the agent page or with maritime triggers create nightly-report --type cron --cron "0 9 * * *" --prompt "..." (see Triggers & schedules). One gotcha worth knowing up front: timers inside a sleeping VM do not fire, so scheduled work belongs in a trigger, not in an in-process scheduler.

Migrate from AWS

What moves: Scheduled Lambda functions, EventBridge cron rules, ECS scheduled tasks, and bots or workers running on EC2 instances. Lambda env vars, Secrets Manager entries, and SSM parameters become encrypted agent env vars.

# Inventory with a read-only IAM user (ReadOnlyAccess policy)
aws events list-rules --query 'Rules[?ScheduleExpression!=null].[Name,ScheduleExpression,State]' --output table
aws lambda list-functions --query 'Functions[].[FunctionName,Runtime]' --output table

EventBridge cron has six fields with ? placeholders; Maritime triggers use standard five-field cron. cron(0 12 * * ? *) becomes 0 12 * * *, and rate(5 minutes) becomes */5 * * * *.

What stays: S3, DynamoDB, RDS, and Route 53 stay on AWS. Your agent keeps calling them with scoped credentials. VPC-only resources (a private RDS, anything without a public endpoint) need a private network: run maritime connect on a machine inside the VPC to publish them to your agents (see Networks), or flag them before deciding.

Read the full AWS runbook →

Migrate from Google Cloud

What moves: Cloud Scheduler jobs, scheduled Cloud Functions, Cloud Run jobs, and bots on Compute Engine. Secret Manager values move into encrypted agent env vars. Cloud Scheduler already uses five-field cron plus a timezone, so schedules map nearly 1:1.

# Inventory with a service account holding roles/viewer
gcloud scheduler jobs list --location=us-central1
gcloud functions list && gcloud run jobs list

What stays: Cloud Storage, Cloud SQL, BigQuery, Firestore, and Cloud DNS stay. Keep using them from the agent over public endpoints. A Cloud SQL instance with only a private IP needs a private network: publish it with maritime connect from a VM in the same VPC (see Networks).

Read the full Google Cloud runbook →

Migrate from Azure

What moves: Azure Functions with timer triggers, Container Apps jobs, Logic Apps automations, and bots on Azure VMs. Key Vault secrets move into encrypted agent env vars.

# Inventory with a service principal holding the Reader role
az functionapp list --output table
az containerapp job list --output table

Azure NCRONTAB has six fields including seconds. 0 0 12 * * * becomes 0 12 * * * on a Maritime trigger.

What stays: Blob Storage, Azure SQL, Cosmos DB, and Azure DNS stay. Service Bus consumers only move if the producer can hit a webhook instead, or the agent polls on a schedule. Private endpoints inside a VNet need a private network: publish them with maritime connect from a VM in the VNet (see Networks).

Read the full Azure runbook →

Migrate from Cloudflare

What moves: Workers on cron triggers, plus their KV and D1 data via export. A Worker that serves HTTP maps to a public web agent, but Workers runtime bindings (env.KV and friends) must become plain API calls, which is porting work.

# Inventory with a custom read-only API token (never the Global API Key)
npx wrangler deployments list --name <worker>
npx wrangler d1 export my-db --remote --output=dump.sql

What stays: Keep your DNS zones on Cloudflare, along with Pages sites and R2 buckets. R2 is S3-compatible, so the agent keeps using it directly. Durable Object alarms and Queues are Cloudflare-runtime constructs; moving those is a rewrite of the trigger model.

Read the full Cloudflare runbook →

Migrate from Vercel

What moves: Cron jobs from vercel.json and the API routes they hit. The common case is moving only the crons and background work while the site stays on Vercel. Often the fastest path is recreating the cron logic as instructions to a framework agent rather than porting the route.

# Source of truth is your git repo: read the crons block in vercel.json
vercel project ls
vercel env pull .env.production --environment=production

What stays: The Next.js site itself, plus Vercel Postgres, KV, and Blob. Copy connection strings into the agent env and keep using them. Domains and DNS stay.

Read the full Vercel runbook →

Migrate from Heroku

What moves: Heroku Scheduler jobs, worker and clock dynos, and config vars. Scheduler only offers three frequencies, so real cron on a Maritime trigger is an upgrade. A worker that only reacts to events can auto-sleep, which usually costs less than an always-running dyno.

heroku apps --json
heroku ps -a my-app
heroku config -a my-app --shell   # write to a local .env, import, delete the file

What stays: Heroku Postgres and Redis add-ons stay: pull DATABASE_URL and REDIS_URL into the agent env and keep using them. The web dyno stays if it serves your site.

Read the full Heroku runbook →

Migrate from Fly.io

What moves: Machines, including scheduled ones, and fly.toml worker processes. Fly apps are already containers with a Dockerfile, so pointing maritime create at the same repo is nearly direct. Fly schedule granularity (hourly, daily) upgrades to full cron on Maritime.

# Inventory with a read-only org token: fly tokens create readonly
fly apps list
fly machine list -a my-app && fly config show -a my-app

What stays: Fly Postgres and volumes stay. A database that only listens on the private 6PN network needs a public connection option, or a private network published with maritime connect from a Fly Machine (see Networks).

Read the full Fly.io runbook →

Migrate from Render

What moves: Cron job services (a cron expression plus a command, which maps 1:1 to an agent trigger) and background workers. Env groups and per-service env vars move into encrypted agent env vars.

# Source of truth: render.yaml in your repo, or the REST API
curl -s https://api.render.com/v1/services -H "Authorization: Bearer $RENDER_API_KEY"

What stays: Web services and static sites stay if they serve your site. Render Postgres and Key Value stay: use the external connection string from the agent, since internal-only URLs will not resolve from Maritime.

Read the full Render runbook →

Migrate from DigitalOcean

What moves: App Platform workers and jobs, DO Functions with cron triggers, and bots on droplets (the droplet interior is the VPS guide below). App spec env values move into encrypted agent env vars.

# Inventory with a custom scoped token (read scopes only)
doctl apps list && doctl apps spec get <app-id>
doctl serverless triggers list

What stays: Spaces buckets are S3-compatible, so keep using them from the agent. Managed databases stay: use the public connection string and update trusted sources. VPC-only databases need a private network: publish them with maritime connect from a Droplet in the VPC (see Networks).

Read the full DigitalOcean runbook →

Migrate from a VPS

What moves: The classic case: a $5 box running your bot under tmux. Crontab lines, systemd services and timers, pm2 and supervisor processes, tmux and screen sessions, and Docker containers all inventory over SSH and land as agents with triggers.

ssh me@box 'crontab -l; systemctl list-timers; pm2 ls; tmux ls; docker ps'

What stays: Anything else on the box: a localhost Postgres or Redis, nginx sites, backups. Every localhost connection string in the env is a decision point, because localhost breaks when the worker moves. Check for other tenants before canceling the server.

Read the full a VPS runbook →