Take your agent to production

Deploy your personal agent to Railway.

Let's deploy the agent we built in the previous page to Railway. We'll give it a stable HTTPS endpoint, use Postgres for storage, and keep using the same Slack app.

We'll need a Railway account, the $5 hobby plan should work fine, but I recommend the $20 pro plan.

Prepare your agent

Let's start by updating the agent to use Postgres for storage and support production authorization.

Update personal_agent.py to:

personal_agent.py
from os import environ, getenv

from agno.agent import Agent
from agno.db.postgres import PostgresDb, create_postgres_engine
from agno.db.sqlite import SqliteDb
from agno.fs import FileSystem
from agno.os import AgentOS
from agno.os.interfaces.slack import Slack

if database_url := getenv("DATABASE_URL"):
    db = PostgresDb(
        db_engine=create_postgres_engine(database_url),
    )
else:
    db = SqliteDb(db_file="personal_agent.db")

fs = FileSystem(db, namespace="personal-agent/{user_id}")

agent_instructions = """You are Pip, the user's personal agent.
Help them keep track of their projects, tasks, decisions, and useful notes
so they can pick up where they left off.

Be warm, direct, and practical. Use natural language and keep replies brief.
When the user asks for an update, lead with what needs their attention
and the next useful step. Acknowledge progress without making a big deal of it.
Adapt to how the user likes to work and communicate.

Keep project briefs, tasks, decisions, and useful notes in your filesystem.
Start with a simple structure, group related information together, and split
files by project or topic when that makes them easier to maintain.
Follow any organization the user requests.

Track task completion and due dates when provided. Record decisions with
their reasoning so the user can revisit them later. Keep the user's stated
commitments separate from your suggestions.

Read the relevant files before answering questions about saved information
or making changes. Create new files as needed. Preserve unrelated entries
when updating existing files. Ask when a missing detail matters; otherwise,
work with what you have.

Only say something is saved or updated after the file tool succeeds.
Confirm what changed in a sentence or two.
"""

agent = Agent(
    name="Pip",
    model="openai:gpt-5.6",
    db=db,
    tools=[fs.tools()],
    instructions=[agent_instructions, fs.instructions()],
    add_history_to_context=True,
    add_datetime_to_context=True,
)

agent_os = AgentOS(
    agents=[agent],
    db=db,
    interfaces=[
        Slack(
            agent=agent,
            token=environ["SLACK_TOKEN"],
            signing_secret=environ["SLACK_SIGNING_SECRET"],
            resolve_user_identity=True,
        )
    ],
    authorization=bool(getenv("JWT_VERIFICATION_KEY")),
    tracing=True,
)
app = agent_os.get_app()

if __name__ == "__main__":
    agent_os.serve(app="personal_agent:app", reload=True)

Our personal_agent.py now switches to Postgres when DATABASE_URL is set, and enables JWT authorization when JWT_VERIFICATION_KEY is set. The deployment image will run the server without hot reload.

Add the deployment files

We need a few files to deploy our agent to Railway.

  1. a Dockerfile to build the app image.
  2. a .gitignore to exclude local data and credentials from Git.

Add the production dependencies

Add Postgres, Pgvector and the cryptography package used to verify JWT signatures:

uv add "agno[os,sqlite,slack,postgres,pgvector]" cryptography

This updates pyproject.toml and uv.lock.

Keep local data and credentials out of Git

Create .gitignore:

.gitignore
.venv/
__pycache__/
.env
.env.*
*.db
*.db-*

Define the app image

Create Dockerfile in the project root:

Dockerfile
FROM agnohq/python:3.14
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --python 3.14 --locked --no-dev --no-install-project
COPY personal_agent.py ./
CMD ["sh", "-c", "exec uv run --no-sync uvicorn personal_agent:app --host 0.0.0.0 --port ${PORT:-8000}"]

Railway detects the Dockerfile automatically. The Agno image provides Python 3.14 and uv. The build installs your locked dependencies and copies only the app and dependency files.

The start command uses Railway's PORT environment variable. For example, if Railway sets PORT=8080, Uvicorn listens on port 8080. The 8000 in ${PORT:-8000} is the fallback when PORT is unset or empty.

Why Postgres?

The app's container filesystem is ephemeral. Postgres stores the agent's files, sessions, and traces separately, so they survive app restarts and redeployments.

Deploy to Railway

  1. Create an empty GitHub repository for this project (call it personal-agent). From your project directory, initialize Git and push the deployment files. Replace the remote URL below with your repository's URL:

    git init
    git add .
    git commit -m "Init personal agent"
    git branch -M main
    git remote add origin https://github.com/YOUR_USERNAME/personal-agent.git
    git push -u origin main
  2. Open Railway, create a New project and Deploy from GitHub repo. Give it your repository URL and grant access if private. The initial app deployment will need the variables below before it can start.

  3. In the same project, choose +AddDatabasePostgreSQL. Wait for the database to be ready. Keep its persistent volume attached.

  4. Open the app service's Variables tab and configure:

    VariableValue
    OPENAI_API_KEYYour OpenAI API key
    SLACK_TOKENThe bot token for your existing Slack app
    SLACK_SIGNING_SECRETThe signing secret for that Slack app
    DATABASE_URLA reference to the Postgres service's DATABASE_URL

    Use Railway's variable reference picker for DATABASE_URL. For a database service named Postgres, the reference is ${{Postgres.DATABASE_URL}}. This keeps the database credentials in Railway.

  5. Open the app service's settings, set the Healthcheck Path to /health. Apply the variable changes and deploy. Keep the app at one replica for this walkthrough.

  6. Under Networking, generate a public domain. Set its target port to the port shown in the Uvicorn startup log, such as 8080. It must match the app's listening port.

  7. View the deployment logs and wait for it to be healthy.

  8. Copy the service's https://...up.railway.app URL and open /docs on that origin to check the API docs. Check the deployment logs if startup fails.

AgentOS API documentation for the personal agent deployed on Railway

Connect and authorize the Control Plane

Connect with the generated security key

Open os.agno.com, select +New AgentOSLive Connection, and enter your Railway URL. Name it Personal Agent and set Token-Based Authorization (JWT) ON.

Connect the personal agent to the AgentOS Control Plane with JWT authorization

Click Connect and copy the generated public verification key.

Add the JWT verification key to your service

Open your service's Variables tab in Railway, add JWT_VERIFICATION_KEY and paste the complete public key, including the BEGIN PUBLIC KEY and END PUBLIC KEY lines. Save and deploy the change.

Monitor the deployment logs and wait for it to be healthy. You now have JWT authorization enabled.

Switch Slack to your hosted agent

Open your app at Your Apps and update both endpoints:

SettingNew URL
Event SubscriptionsRequest URLhttps://YOUR-SERVICE.up.railway.app/slack/events
Interactivity & ShortcutsRequest URLhttps://YOUR-SERVICE.up.railway.app/slack/interactions

Wait for Slack to verify the events URL and save the changes. Use the actual service URL from Railway.

Stop your local Python server and ngrok. In a new Slack message, ask:

Do i have any open projects?

The agent is new, so no projects are listed.

Your agent is now live

After deploying to Railway, your agent is now live. You can use it in Slack and view the sessions, traces and memory in the Control Plane.