Skip to main content

Writing your first Deployment

This is a tutorial to author your first deployment on the Outerbounds platform.

Our "Hello World!" Deployment

For this tutorial, assume you have a simple Flask service that looks like this.

my-first-project/
└── first-service.py

The code for first-service.py is really simple.

from flask import Flask
import os
import time

app = Flask(__name__)


@app.route("/")
def hello_world():
return {"message": "Hello, World!"}


if __name__ == "__main__":
port = int(os.environ.get("PORT", 8000))
app.run(host="0.0.0.0", port=port)

Managing requirements

Outerbounds can automatically package all your dependencies into a docker container to run your deployment, just like Metaflow tasks.

To utilize this, you can choose to declare your dependencies in a few different formats. One of the easiest ways is to simply create a requirements.txt file at the root of your folder.

💡 To read about all the ways in which you can manage dependencies, go to the Managing Dependencies page.

For this example, lets just create a simple requirements.txt file that specifies the one dependency we have, flask.

my-first-project/
└── first-service.py
└── requirements.txt

Defining a config file

You can define several options that configure the behavior of your deployment. To do this, you can either pass in the values of the options using the CLI, or create a config file. The recommended best practice is to define as much configuration as possible in the config file, and use the CLI options ocassionally to override the config file or for any rapid prototyping.

Let's create a simple config file called config.yaml

name: hello-world 
port: 8000
auth:
type: API

commands:
- python main.py

Place this in the root of your folder.

my-first-project/
└── first-service.py
└── requirements.txt
└── config.yaml

Deploying your service

Now that we have the service code ready, and the dependencies declared, we are ready to launch our deployment.

To get started, run: outerbounds app deploy --config-file config.yaml.

This should print an output like this:

2025-10-29 11:14:13.915 🚀 Deploying hello-world to the Outerbounds platform...
2025-10-29 11:14:13.915 📦 Packaging directories : /Users/ukashyap/Work/inference-examples/tutorial-0-hello-world/endpoint
2025-10-29 11:14:13.916 📦 Using dependencies from requirements.txt: /Users/ukashyap/Work/inference-examples/tutorial-0-hello-world/endpoint/requirements.txt
2025-10-29 11:14:13.931 🍳 Baking [hello-world] ...
2025-10-29 11:14:13.931 🐍 Python: 3.9.12
2025-10-29 11:14:13.931 📦 PyPI packages:
2025-10-29 11:14:13.931 🔧 flask: 3.0.2
2025-10-29 11:14:13.931 🔧 Werkzeug: 3.0.1
2025-10-29 11:14:13.931 🔧 torch:
2025-10-29 11:14:13.931 🔧 matplotlib:
2025-10-29 11:14:13.931 🔧 requests: >=2.21.0
2025-10-29 11:14:13.931 🔧 boto3: >=1.14.0
2025-10-29 11:14:13.931 🏗️ Base image: 006988687827.dkr.ecr.us-west-2.amazonaws.com/obptask-python:master-3da1222c-1757363580
2025-10-29 11:14:57.979 🏁 Baked [hello-world] in 44.05 seconds!
2025-10-29 11:14:57.981 🐳 Using the docker image : fast-bakery.dev-valay.outerbounds.xyz/default/celebrated-fish:vx6wpgl0vm-oci-zstd
2025-10-29 11:14:58.880 💾 Code package saved to : s3://obp-475b0e-metaflow/metaflow/mf.obp-apps/ab/ab1d21bede2cc6f8dd3b798e2f1a190138b6619b
2025-10-29 11:14:59.082 🚀 Upgrading endpoint `hello-world`....
2025-10-29 11:15:22.487 ⏳ 1 new worker(s) pending. Total pending (1)to serve traffic
2025-10-29 11:16:46.804 🚀 1 worker(s) started running. Total running (1)erve traffic
2025-10-29 11:16:46.804 ✅ First worker came online
2025-10-29 11:16:46.804 🎉 All workers are now running
2025-10-29 11:17:26.439 💊 Endpoint deployment status: completed ady to serve traffic
2025-10-29 11:17:26.439 💊 Running last minute readiness check for c-3si29v...
2025-10-29 11:17:46.756 💊 Endpoint c-3si29v is ready to serve traffic on the URL: https://api-c-3si29v.dev-valay.outerbounds.xyz
2025-10-29 11:17:46.801 💊 Endpoint hello-world (c-3si29v) deployed! Endpoint available on the URL: https://api-c-3si29v.dev-valay.outerbounds.xyz

At the end, you will see a URL printed on the terminal that you can use to access your deployed endpoint.

Understanding the deploy command

Click to look at the full breakdown of the command above.

Command:

  • app deploy is the command used to deploy either a new deployment or update an existing deployment.
  • --config-file defines the location of the config file that contains the configuration for your deployment.

Config File:

  • name is the globally unique identifier of your deployment. No two deployments can have the same name.
  • port is the port number where your service is listening. For example, in our Flask example above, we start the server at port 8000, and then pass the same thing in our command.
  • auth.type: This can take two values, either API or Browser.
    • If you choose API, then the Deployment will use token based authentication to be used by programatic clients like cURL, python, etc. See accessing your deployed endpoints for an example.
    • If you choose Browser, then the UI will be accessible using the same SSO authentication as the one used for the Outerbounds platform. This means, if you're logged into the outerbounds platform and can access the UI, then you will be able to access your Deployment.
  • commands: Finally, you need to provide the command used the launch your service itself. This should be the same as what you'd use if you were trying to run the service locally. In our example, this is a simple python main.py. You can provide multiple commands if needed.

Accessing your deployed endpoint

Using cURL, Python, or whatever language/framework of your choice, you can construct a request for your endpoint as you would. The only additional thing you need to do is attach auth headers to your request so that they can be authenticated.

Use the following function to get the headers required to make your call.

def auth():
from metaflow.metaflow_config_funcs import init_config
conf = init_config()
if conf:
headers = {'x-api-key': conf['METAFLOW_SERVICE_AUTH_KEY']}
else:
headers = json.loads(os.environ['METAFLOW_SERVICE_HEADERS'])
return headers

Here's a full client that you can use to call the endpoint deployed here. Make sure to replace the URL with the URL of your deployed endpoint.

import requests
import os

def auth():
from metaflow.metaflow_config_funcs import init_config
conf = init_config()
if conf:
headers = {'x-api-key': conf['METAFLOW_SERVICE_AUTH_KEY']}
else:
headers = json.loads(os.environ['METAFLOW_SERVICE_HEADERS'])
return headers

# TODO: Change to your own endpoint
url = "https://api-c-3si29v.dev-valay.outerbounds.xyz"
print(requests.get(url, headers=auth()).text)

Note that the auth() can work in all of the following cases:

  • Running locally from a script when you have a metaflow config.
  • Running from inside a local/remote Metaflow task.
  • Running from any environment where your have an Outerbounds Machine User configured.

Up Next

Take a look at deployments deep dive to get a deeper understanding of how to work with deployments, or go to outerbounds/inference-examples Github repository to hit the ground running!