Python Backend deployed on Marlin TEEs

Deploying a Python Backend on Oyster: Step-by-Step Technical Guide

Introduction :rocket:

Deploying secure and scalable backends is a core challenge for modern developers. In this article, you’ll learn how to build an asynchronous TCP Echo Server in Python, containerize it with Docker, deploy it securely on Marlin’s Oyster TEEs and verify its remote attestation.

Prerequisites :technologist:

Before you begin, ensure you have:

  • Basic knowledge of Python and Docker
  • Access to a Linux or macOS terminal (or WSL on Windows)
  • An Ethereum wallet private key with 0.001 ETH and 1 USDC on the Arbitrum One network
  • Docker installed (Docker installation guide)
  • Oyster CLI installed (Oyster CLI installation guide)

Step 1 - Setting Up Your Development Environment :hammer_and_wrench:

First, verify your development environment is ready:

  • Ensure Python is installed:
python --version
  • Confirm Docker is installed and running:
docker --version
  • (Optional) Use Oyster’s CLI doctor command to check Docker:
oyster-cvm doctor --docker

Step 2 - Creating the Python Asynchronous TCP Echo Server :snake:

We’ll use Python’s asyncio library to create a simple TCP server that echoes messages back to connected clients, including the server’s hostname for clarity.

Create a file named echo-server.py and add the following code:

#!/usr/bin/env python3

import platform
import asyncio

# server will listen on PORT 5000.
PORT_ECHO_TCP = 5000
# server will listen on all available network interfaces.
INADDR_ANY = "0.0.0.0"

SERVER_NAME = platform.node()

# provides methods to handle network events.
class TcpEchoServer(asyncio.Protocol):
   def connection_made(self, transport):
       self.peername = transport.get_extra_info('peername')
       print(f'TCP: Connection from {self.peername}')
       self.transport = transport

   def data_received(self, data):
       in_msg = data.decode().strip()
       print(f'TCP: Received: {in_msg} from {self.peername}')
       out_msg = f'TCP: {SERVER_NAME} received: {in_msg}\n'
       self.transport.write(out_msg.encode())

# The main asynchronous function that gets the current event loop.
async def main():
   loop = asyncio.get_running_loop()

   print(f"Starting TCP server on port {PORT_ECHO_TCP}")
   tcp_server = await loop.create_server(
       lambda: TcpEchoServer(),
       INADDR_ANY, PORT_ECHO_TCP)

   async with tcp_server:
       await tcp_server.serve_forever()


asyncio.run(main())

To run the server locally:

python echo-server.py

In a new terminal, connect using netcat:

nc localhost 5000

Type messages, and you’ll see them echoed back by the server.

Step 3 - Dockerizing the Server :whale:

Containerizing your application ensures consistency across deployments.

Create a Dockerfile in your project directory:

FROM alpine:3.11

RUN apk add python3
COPY echo-server.py /echo-server.py

CMD ["python3", "-u", "/echo-server.py"]

EXPOSE 5000/tcp

Build and push the Docker image:

Replace <username> with your Docker Hub username.

sudo docker login
sudo docker build -t <username>/echo-server:latest .
sudo docker push <username>/echo-server:latest

Test the Docker image locally:

docker run --rm -p 5000:5000 --name echo-server <username>/echo-server

Connect with nc localhost 5000 as before to verify functionality.

Step 4 - Deploying on Marlin’s Oyster TEEs :oyster:

Creating a Docker Compose File

Create a file named docker-compose.yml describing the service being deployed after replacing the <username>/echo-server with the docker image created in the previous step:

services:
  echo-server:
    image: <username>/echo-server
    network_mode: host
    restart: unless-stopped

Deploying the Enclave

Use the Oyster CLI to deploy your containerized backend in a secure enclave.

# replace <key> with private key of the wallet

# for amd64
oyster-cvm deploy --wallet-private-key <key> --duration-in-minutes 15 --docker-compose docker-compose.yml --arch amd64

# for arm64
oyster-cvm deploy --wallet-private-key <key> --duration-in-minutes 15 --docker-compose docker-compose.yml

Make a note of the “Computed image id” and the enclave IP printed in the logs. These are essential for future verification and access.

Connecting to Your Deployed Server

Once deployed, interact with your echo server using:

nc <ip> 5000

Replace <ip> with the enclave IP from the deployment logs. You should see your messages echoed back, confirming a successful deployment.

Example Output:

$ nc <ip> 5000
We love oyster!
TCP: (none) received: We love oyster!
Hello from Python Backend!
TCP: (none) received: Hello from Python Backend!

Step 5 - Verifying Remote Attestation :shield:

Oyster supports remote attestation, ensuring your backend runs in a trusted environment.

To verify:

# Replace <ip> and <image_id> with values from your deployment
oyster-cvm verify --enclave-ip <ip> --image-id <image_id>

A successful verification will confirm your backend is running securely.

Conclusion :tada:

By following this guide, you have:

  • Built an asynchronous TCP Echo Server in Python
  • Containerized your application with Docker
  • Deployed and verified your backend on Marlin’s Oyster TEE using confidential computing

This workflow ensures your backend is scalable and secure. Next, you can extend your backend’s functionality, explore Oyster’s advanced features, or integrate with other services for a production-ready deployment.