Serverless vs containers for bots and APIs

Use serverless for short, event-driven code such as a webhook or an API with spiky traffic. Use a container for processes that must stay running, such as Gateway bots, WebSockets and queue workers.

Updated 6 min read

Learning objectives

After reading this article you will be able to:

  • Understand the difference between a serverless function and a container
  • Recognize limits such as timeouts, cold starts and persistent connections
  • Pick the right model for a bot or an API
On this page

Almost everyone who ships a bot or an API hits the same question: deploy a function that runs only when called, or a container that stays up all the time. Both work, but they solve different problems, and the wrong pick shows up as timeouts, a bot that drops offline, or a bill that does not add up.

The comparison below uses the criteria that matter in practice: execution time, persistent connections, cold starts, state and cost. The limits quoted come from the AWS (Lambda), Google Cloud (Cloud Run) and Discord documentation. These values change from time to time, so check each provider's official page before you design around them.

Serverlessnew environment per callrequestcold startenvironment goneContaineralways-on processstartone continuous process serves everythingtime
In serverless, each call can create an environment, with a cold start, that then vanishes; the container starts once and serves everything.

What is serverless?#

With serverless (also called FaaS, "function as a service"), you ship a function. The provider creates an execution environment when a request or event arrives, runs the function, then freezes or discards the environment. You do not pick a machine or maintain an operating system, and the number of environments follows demand, down to zero when nobody calls.

The classic example is AWS Lambda. A Telegram webhook in Python fits in one function:

python
import json
import os
import urllib.request


def handler(event, context):
    # Reject calls without the secret_token set in setWebhook
    headers = event.get("headers") or {}
    if headers.get("x-telegram-bot-api-secret-token") != os.environ["WEBHOOK_SECRET"]:
        return {"statusCode": 401, "body": "unauthorized"}

    update = json.loads(event["body"])
    msg = update.get("message")
    if msg and msg.get("text") == "/ping":
        payload = json.dumps({"chat_id": msg["chat"]["id"], "text": "pong"}).encode()
        req = urllib.request.Request(
            f"https://api.telegram.org/bot{os.environ['BOT_TOKEN']}/sendMessage",
            data=payload,
            headers={"Content-Type": "application/json"},
        )
        urllib.request.urlopen(req, timeout=5)
    return {"statusCode": 200, "body": "ok"}

With a Function URL, the request body arrives as text in event["body"], and the X-Telegram-Bot-Api-Secret-Token header carries the secret_token you set in setWebhook. Each message becomes one invocation. Between invocations, none of your processes is running. Compare that with long polling, where a process keeps asking for updates.

What is a container?#

A container is an isolated process, packaged with its dependencies, that runs continuously until you stop it. For the technical background, see what is Docker and containers vs virtual machines. For a developer, the deciding difference is this: your code starts once and keeps memory, connections and timers open.

A Discord bot using the Gateway is the typical case:

js
import { Client, Events, GatewayIntentBits } from "discord.js";

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, (c) => console.log(`Online as ${c.user.tag}`));

client.on(Events.InteractionCreate, async (interaction) => {
  if (interaction.isChatInputCommand() && interaction.commandName === "ping") {
    await interaction.reply("pong");
  }
});

client.login(process.env.DISCORD_TOKEN);

This process has to stay alive to receive events. If it dies, the bot goes offline (see how to keep a bot running 24/7).

What are the limits of serverless?#

Limits vary by provider, but those of standard AWS Lambda functions show the shape (newer Lambda variants have their own limits):

  • Duration: one invocation runs for up to 900 seconds (15 minutes).
  • Memory: 128 MB to 10,240 MB, with CPU scaling in proportion to the memory you set.
  • Payload: 6 MB for the request and for the response on synchronous calls.
  • Disk: /tmp between 512 MB and 10,240 MB, with no guarantee it persists.
  • Environment: Lambda reuses environments for a while but retires them every few hours, so code cannot assume state survives.

Google Cloud Run, which runs containers on demand, shows the line between the models is blurrier than it looks: its default request timeout is 5 minutes and can go up to 60 minutes. Even so, the application still has to accept that its process can be created and destroyed at any moment.

What is a cold start and when does it hurt?#

A cold start is the cost of the first invocation in a new environment: downloading the code, starting the runtime and running the initialization outside the handler. According to AWS, it ranges from under 100 ms to over 1 second and happens in under 1% of invocations. For an API with regular traffic, that is nearly irrelevant. For an endpoint called rarely, almost every call pays the delay.

It matters when the response deadline is short. Discord requires the initial response to an interaction within 3 seconds, and the interaction token is valid for 15 minutes for follow-ups. A cold start plus a slow database query can blow that deadline. The fix is to send a deferred response right away and edit the message later.

Providers offer mitigation, such as Lambda's provisioned concurrency, which keeps environments ready. At that point you are paying for idle capacity again, which is what a container already does.

Why don't Gateway bots and WebSockets fit serverless?#

The Discord Gateway is a persistent WebSocket. The connection needs heartbeats at a regular interval, and if your app fails to send one in time, Discord closes the connection and you have to resume the session. A function capped at 15 minutes with a disposable environment cannot hold that. The same goes for any WebSocket server, for workers that consume a queue continuously and for WhatsApp bots that depend on an open session.

There are two real exceptions. Discord bots can receive commands over HTTP (Interactions through an endpoint) without the Gateway, and a function is enough there, but you lose events such as messages and presence. And a Telegram bot in webhook mode works well on serverless, because each update arrives as a request.

How do the two compare side by side?#

Criterion Serverless (function) Container
Deploy unit A function and its handler A process or image
Run time Limited (e.g. 15 min on Lambda) Continuous, as long as the process lives
Persistent connection No Yes
In-memory state Not reliable Kept while the process lives
Cold start Yes Only when the process starts
Scaling Automatic per request, down to zero You set the RAM and instance count
Typical billing Per invocation and duration Per reserved capacity
Good for Webhooks, short jobs, spiky traffic Bots, WebSockets, queues, steady APIs
Watch out for Timeouts, payload limits, cost at high volume Restarts, logs and sizing are on you

There is no universal winner. Cost depends on the traffic pattern: few calls favor paying per invocation, steady load favors fixed capacity. Run the numbers with your own volume before deciding.

Which one should you pick?#

  • Discord Gateway bot, music bot or WhatsApp bot with an open session (Baileys, whatsapp-web.js): container.
  • Telegram bot: either works. Webhooks fit serverless; long polling needs a running process.
  • REST API with steady traffic: container, for predictable cost and no cold start. See how to host an API.
  • API or webhook with rare spikes: serverless tends to pay off.
  • Short scheduled task: serverless, or a cron job inside a container.
  • Long task, batch processing: container, because of the duration cap.

A hybrid is also common: the core runs in a container and one or two functions handle occasional events.

How to do this on Square Cloud#

Square Cloud uses the container model: each application runs in an isolated container that is always on, with no cold start and no sleep mode. Automatic restart is on by default for dashboard deploys and brings the app back when the process exits with an error. Because the container never sleeps and the proxy does not cut long WebSocket connections, Gateway bots, WebSocket servers and workers run without changes.

You deploy by ZIP upload in the dashboard, with the CLI, through GitHub or with the VS Code extension, and choose from several runtimes, such as Node.js, Python, Go and Java; see the ones available today on Runtimes.

Scaling is vertical (you change each application's RAM) or horizontal through load balancing: from the Standard plan up, you put the same custom domain on several applications, served round-robin. There is no autoscaling: you set the capacity. The full list is in Square Cloud features.

Deploy

Push from GitHub, the CLI or a ZIP and go live in seconds

Learn more

FAQs

Is serverless cheaper than containers?

It depends on usage. With few, scattered requests, paying per invocation is usually cheaper. With steady traffic or an always-on process, a fixed-size container tends to have more predictable costs.

Can I run a Discord bot built with discord.js on serverless?

Not with the Gateway model, because it needs a WebSocket connection open all the time. It only works if the bot uses HTTP-based Interactions alone, which Discord sends as plain requests.

What is a cold start?

It is the extra time of the first invocation in a new environment: downloading the code, starting the runtime and running initialization. Later invocations that reuse the environment skip that cost.

Does serverless really have no servers?

It has servers. The name means you do not provision or maintain the machine: the provider decides where and when your code runs.

Next articleWhy is my server or app slow? CPU steal, memory and I/OA slow app almost always has a measurable bottleneck: CPU steal, throttling, memory, disk, network or the code itself. Learn how to measure each one and fix it.

More in Deploy

See all 33 articles