How to Migrate Local AI Agent to the Cloud Without Losing State
You built an agent on your laptop, it works, and now it needs to run somewhere that does not sleep when you close the lid. The job is easy to state and easy to botch: migrate local AI agent to the cloud without wiping its memory (conversation history, embeddings, a vector store or a SQLite file) or its config (environment variables, API keys, model settings). Lose those and you have not migrated an agent, you have redeployed a blank one.
Most "just deploy it" guides skip the state entirely and quietly reset your agent. This one treats memory and config as first-class cargo. If you are still deciding whether to run it yourself or on a platform, read self-hosted vs managed AI agents first, then come back for the mechanics.
Before touching anything, understand the shape of the job. To migrate a local AI agent to the cloud you inventory what is local, containerize the process, move secrets safely, move the data volume, verify parity, and cut over. Six moves, in that order.
The migration checklist
Keep this list open while you work:
Inventory every piece of local state: env vars, API keys, config files, the memory store, and the persistent working directory.
Pin your dependencies (a lockfile or an exact
requirements.txt) so the cloud build matches your laptop.Write a Dockerfile that builds the agent but does not bake in a single secret.
Move secrets as environment variables or a secret store, never committed to the image or the repo.
Copy the data volume (vector store, SQLite, working dir) to the cloud target.
Run both copies side by side and verify parity on a known query.
Cut over, watch logs, and keep the laptop copy until you trust the cloud one.
Step 1: Inventory what lives on your laptop
Agents accumulate hidden state. Before you can move it, list it. Walk through these categories and write down the actual path or name for each:
Environment variables and API keys: usually in a
.envfile or your shell profile. This is your OpenAI or Anthropic key, database URLs, and feature flags.Config files: model names, temperature, system prompts, tool definitions. Often JSON or YAML in the project root.
Memory and vector store: a local Chroma instance, a SQLite database, a
pgvectortable, or a folder of embeddings. This is the part people forget, and it is the part that hurts to lose.The persistent working directory: if the agent writes files (notes, scraped data, generated code), find that directory. On a laptop it is often just
./dataor the current working directory, which does not survive a container restart unless you plan for it.
Write it down in a small table with three columns: what it is, where it lives now, and how it will travel (rebuilt from the image, injected as a secret, or copied as a volume). That table is your migration plan in miniature, and it is the difference between a clean cutover and discovering at 11 p.m. that the agent's memory never actually left your laptop.
A quick way to find state is to look for anything the agent opens for writing. Grep the code for file paths, database connection strings, and any directory it creates on startup.
Step 2: Containerize the agent
A container makes the agent reproducible, so it behaves the same on the cloud host as on your machine. Here is a minimal Dockerfile for a Python agent. Notice that no secret and no data appears in it.
FROM python:3.12-slim
WORKDIR /app
# Dependencies first so this layer caches
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Then the code
COPY . .
# The writable directory lives on a mounted volume,
# not inside the image. We just declare it here.
VOLUME ["/data"]
ENV AGENT_DATA_DIR=/data
CMD ["python", "agent.py"]FROM python:3.12-slim
WORKDIR /app
# Dependencies first so this layer caches
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Then the code
COPY . .
# The writable directory lives on a mounted volume,
# not inside the image. We just declare it here.
VOLUME ["/data"]
ENV AGENT_DATA_DIR=/data
CMD ["python", "agent.py"]Two rules make this migration-safe. First, the image contains code only, never secrets and never the memory store. Second, the writable path (/data here) is a declared volume, so state lives outside the image and survives restarts and rebuilds.
Build and test it locally before you push it anywhere:
docker build -t my-agent:latest .
docker run --rm --env-file .env -v "$(pwd)/data:/data" my-agent:latestdocker build -t my-agent:latest .
docker run --rm --env-file .env -v "$(pwd)/data:/data" my-agent:latestIf it runs locally with your real data mounted in, you have a portable unit. If it only works when launched from your project folder with your shell environment loaded, you have more inventory to do.
Step 3: Move secrets safely, not baked into the image
This is the step people get wrong in a way that ends up on a security blog later. Never copy your .env into the image, and never commit keys to the repo. A key baked into a Docker layer is extractable by anyone who pulls the image, even if you delete it in a later layer.
Move secrets as configuration that the runtime injects, not as files the build embeds:
On a plain Docker host, pass them at run time with
--env-fileor individual-eflags, and keep the.envfile off the image and out of git via.dockerignoreand.gitignore.On Kubernetes, use a Secret object mounted as environment variables.
On a managed platform, use its secret manager and reference the values by name.
In code, read them from the environment. Do not hardcode:
import os
api_key = os.environ["OPENAI_API_KEY"] # fails loudly if missing
data_dir = os.environ.get("AGENT_DATA_DIR", "/data")import os
api_key = os.environ["OPENAI_API_KEY"] # fails loudly if missing
data_dir = os.environ.get("AGENT_DATA_DIR", "/data")The rule to remember: config and secrets are injected at run time, code is baked at build time, and the two never mix. Rotate any key that has ever touched a git history or an image layer.
Step 4: Move the data volume without losing memory
Now move the actual state: the vector store, the SQLite memory, the working directory. The trick is to move the bytes, not to re-create them, so embeddings and history come across intact.
If your local data is a folder, the simplest reliable move is an archive plus a copy over SSH:
# On your laptop: package the agent's data directory
tar czf agent-data.tar.gz -C ./data .
# Copy it to the cloud host
scp agent-data.tar.gz user@your-host:/tmp/
# On the cloud host: unpack into the volume the container mounts
mkdir -p /srv/agent/data
tar xzf /tmp/agent-data.tar.gz -C /srv/agent/data# On your laptop: package the agent's data directory
tar czf agent-data.tar.gz -C ./data .
# Copy it to the cloud host
scp agent-data.tar.gz user@your-host:/tmp/
# On the cloud host: unpack into the volume the container mounts
mkdir -p /srv/agent/data
tar xzf /tmp/agent-data.tar.gz -C /srv/agent/dataThen mount that directory into the container the same way you did locally, so the path inside the container (/data) is identical. Identical paths are what let the agent find its memory without a config change.
A few things that save you pain here:
Stop the local agent before archiving a SQLite or Chroma file, so you copy a consistent snapshot and not a half-written database.
If the memory lives in a managed database (a hosted Postgres with
pgvector), you do not copy files at all. Point the cloud agent at the same database URL, or dump and restore withpg_dump.Preserve the directory layout exactly. A vector store that expects a subfolder will silently start empty if the layout shifts.
One more note on large stores: if the vector store is many gigabytes, the archive and copy can take a while, so run it once to seed the cloud volume, then do a second, quick incremental sync with rsync right before cutover to catch anything that changed since. That keeps the downtime window short even when the data is big.
Step 5: Verify parity, then cut over
Do not delete anything on your laptop yet. Run the cloud copy in parallel and prove it behaves the same.
Ask the agent something only its memory can answer: a fact from an earlier session, or a document you ingested. If the cloud copy answers correctly, the memory came across.
Check that the working directory persists across a container restart. Restart the container and confirm the files are still there. If they vanish, your volume is not actually mounted and you are writing into the ephemeral container layer.
Confirm secrets resolved: no missing-key errors in the logs, and the model responds.
Watch the logs for the first real workload. A tail of the container logs during a live task tells you more than any smoke test.
Have a rollback plan before you flip anything. Because you kept the laptop copy and the archived data volume, rolling back is not a rebuild: you stop the cloud instance, re-point traffic at the local one, and you are back where you started with no data lost. Knowing you can undo the cutover in under a minute is what makes it safe to try during business hours instead of at midnight.
When the cloud copy has answered from memory, survived a restart with its files intact, and run a real task cleanly, cut over. Point your traffic or schedule at the cloud instance, keep the laptop copy cold for a week as a fallback, and only then reclaim the space.
Where a persistent workspace makes this easier
The recurring theme across every step is the same: an agent is its state, not just its code. The hard parts of this migration are all about carrying memory, secrets, and a writable directory across the gap without dropping anything.
This is the specific problem a managed runtime removes. On Sokko, each instance gets a persistent workspace the agent can keep writing files to, secrets are stored securely and injected rather than baked into an image, and the container is supervised so a crash does not lose the volume. You still do the inventory in step 1, because only you know what your agent stores, but steps 2 through 4 collapse into a single migrate step that carries your .env secrets, your memory files (MEMORY.md, USER.md, IDENTITY.md, memory/*), and the rest of your working directory into the instance for you, instead of a weekend of Docker and SSH. It is one CLI command (npm i -g sokko) for anyone who wants it. If you would rather not hand-roll the container and volume plumbing, running any AI agent 24/7 on Sokko walks through the managed path, and if this is your very first deploy, deploying your first agent starts from zero.
Whichever route you take, the discipline is the same. Inventory the state, keep secrets out of the image, move the volume as bytes, verify from memory before you trust it, and keep the old copy until the new one has earned it. Do that and you migrate a local AI agent to the cloud with its memory and config fully intact.