How to Migrate from AWS Lambda to Railway

If you are currently using AWS Lambda and are considering migrating to Railway, this article will walk you through a step-by-step process for migrating your AWS Lambda function to Railway.

Why Migrate from AWS Lambda to Railway?

While AWS Lambda is a powerful tool, there are a few reasons why developers might choose Railway for their projects:

  • Simplified Deployment: Railway abstracts away much of the complexity involved in deployment, providing a seamless experience that doesn’t require deep cloud infrastructure knowledge.
  • Built-in Database Support: Railway makes it easy to connect to and manage databases without needing separate services.
  • Cost-Effectiveness: For smaller projects or developers looking to optimize their spending, Railway offers simpler pricing models, especially for projects that require both hosting and databases.
  • Better Local Development: Railway’s integration with local development environments is straightforward, allowing you to deploy without worrying about complex AWS configurations.

Prerequisites for Migration

Before starting the migration, make sure the following requirements are met:

  • Railway Account: You must have an active Railway account.
  • AWS Admin Access: Ensure you have administrative access to your AWS Management Console.
  • Access to AWS Lambda Functions: Ensure you have access to your AWS Lambda function code, whether from the AWS Management Console or your local machine.
  • Identify Dependencies: List all external resources such as environment variables, and APIs your Lambda functions rely on and add them to Variables in your Railway dashboard.

Step-by-Step Migration Process

  1. Download and open the Function code
  2. Package the Function as a Container Image
  3. Deploy the Container Image to Railway
  4. Test the Deployment

Download and open the Function code

Download the function code and open it in your Code Editor

  • Go to the AWS Management Console, navigate to the Lambda dashboard, select your function, and download the code package.
Download menu from the Lambda dashboard
Download menu from the Lambda dashboard
  • Note the Runtime and Architecture your function is built on
  • Unzip and open the code in your preferred Code Editor

Package the Function as a Container Image

Railway supports several methods of deploying your code, [See]. In this guide, We will deploy our Lambda function to Railway via Docker images directly from Docker Hub.

You must have Docker (minimum version 20.10.10)

There are three ways to build a container image for your Lambda function:

  1. Using an AWS base image: Preloaded with runtime.
  2. Using an AWS OS-only base image: For Compiled language.
  3. Using a non-AWS base image: For another Container registry.

In this guide, we will use the AWS base image.

  • Create a Dockerfile in the same directory as your function. The Dockerfile defines how to build your container image.
Expected file structure including Dockerfile
Expected file structure including Dockerfile
  • Add the following configuration to your Dockerfile:

    # For NodeJs runtime, and Architecture x86_64
    FROM public.ecr.aws/lambda/nodejs:20-x86_64 
    
    # Copy the function code
    COPY index.mjs ${LAMBDA_TASK_ROOT}
    
    # Set the CMD to your handler (could also be done as a parameter
    # override outside of the Dockerfile)
    CMD [ "index.handler" ]
    # Use the AWS Lambda Node.js base image
    FROM public.ecr.aws/lambda/nodejs:20-x86_64
    
    # Copy files or directories and add to the filesystem of the container at the path.
    COPY ${FILES} ${CONTAINER_PATH}
    
    #RUN npm install to install dependencies
    RUN npm install
    
    # Set the Lambda function handler
    CMD ["index.handler"]
  • Build the Docker image: Open up your terminal and run the following command in the directory containing your Dockerfile

     docker build --platform linux/amd64 -t my-railway-function .

    Note: The command specifies the --platform linux/amd64 option to ensure that your container is compatible with the Lambda execution environment regardless of the architecture of your build machine.

  • Test the image locally

    Start the Docker image with the docker run command.

    docker run --platform linux/amd64 -p 9000:8080 --read-only my-railway-function

    From a new terminal window, post an event to the local endpoint.

    In Linux and macOS, run the following curl command:

    curl "<http://localhost:9000/2015-03-31/functions/function/invocations>" -d '{}'

    If your function code requires a payload, you might want to invoke the function with a JSON payload. Example:

    curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}'
  • Deploy your local Docker image to Docker Hub.

Deploy the Container Image to Railway

  • Create an Empty Project in Railway
Empty project option from Railway command palette
Empty project option from Railway command palette
  • Add a new service
Add a Service card in empty project
Add a Service card in empty project
  • Choose Docker Image
Docker Image option in Railway command palette
Docker Image option in Railway command palette
  • Enter name of your image from DockerHub
Prompt to enter the image name
Prompt to enter the image name
  • Deploy your service, and ensure deployment is successful
Successful deployment indicator
Successful deployment indicator
  • Go to Service settings, Under Networking generate a Domain
Generating a public domain in the Railway service
Generating a public domain in the Railway service
  • Enable App Sleep: App Sleep is Railway’s serverless feature that helps reduce service costs by ensuring the application runs only when necessary. It automatically "sleeps" the app during periods of inactivity, and wakes it up when requests are made, minimizing resource usage.
App sleeping configuration
App sleeping configuration

Test the Deployment

  • Invoke your function: Invoke your function using the generated URL and append /2015-03-31/functions/function/invocations to the endpoint.

    Example: <generated-url>/2015-03-31/functions/function/invocations

  • View Logs: Railway provides a built-in logging feature to help you diagnose any issues. Check logs for any errors or warnings that may arise during the initial execution.

  • Verify No invocations on AWS: You can view invocations on your function in AWS Management Console to verify no invocations in AWS.

    Checking for new invocations in AWS Lambda
    Checking for new invocations in AWS Lambda
  • Verify External Connections: If your function depends on external services (like databases or third-party APIs), ensure those connections are working properly in the Railway environment.

Post-Migration Considerations

Monitor and Scaling

Railway handles scaling automatically, and you do not need to go through the hassle of complex scaling configurations.

You can use the HTTP logs to monitor your application.

HTTP Logs tab in Railway
HTTP Logs tab in Railway

Cost Management

Railway has a clear and simple pricing model based on usage. You can set limits to ensure you don’t exceed your desired budget.

Troubleshooting Common Issues

  • External Resource Management: Ensure that all external resources such as environment variables, and APIs required by your Lambda function are properly noted and included in the container image during packaging.
  • Timeouts or Retries: Railway doesn’t have built-in configurations to manage timeouts or retries. You'll need to handle these manually within your application.

Conclusion

Migrating from AWS Lambda to Railway offers a more streamlined developer experience, allowing you to focus on building features rather than managing infrastructure. By following these steps—exporting your Lambda functions, package function as a container image, setting up your Railway environment, and deploying your code—you can smoothly transition from AWS Lambda to Railway. With its easy setup, cost-effective solutions, and powerful database integrations, Railway can be an ideal alternative for many serverless projects.