---
title: Migrate self-hosted Next.js and containers from AWS to Vercel
description: Migrate Next.js apps and HTTP containers from AWS to Vercel. Configure AWS access, assess background workers, and plan testing, DNS cutover, and rollback.
url: "https://vercel.com/kb/guide/migrate-containers-from-aws-to-vercel"
published: 2026-08-04
last_updated: 2026-10-22
authors: Ben Sabic
related_resources:
  - title: "Backends on Vercel: Python, containers, and Daemons"
    url: "https://vercel.com/blog/backends-on-vercel-python-containers-and-daemons"
    description: "Explore how Python, container images, and background workers fit into the Vercel platform."
  - title: "The Complete Guide to Vercel Services"
    url: "https://vercel.com/kb/guide/vercel-services"
    description: "Vercel Services let you deploy multiple frontends and backends in one project on a shared domain. Learn how to define services in vercel.json, route requests by path prefix, and connect services with generated environment variables."
  - title: "Running Docker on Vercel"
    url: "https://vercel.com/kb/guide/docker"
    description: "Learn how to run Docker on Vercel by deploying OCI container images as Vercel Functions, storing them in Vercel Container Registry (VCR), and running containers in Vercel Sandbox."
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Move a self-hosted Next.js app from AWS through Vercel's [Next.js integration](https://vercel.com/docs/frameworks/full-stack/nextjs). Adjacent HTTP services can deploy from a `Dockerfile.vercel` as [Vercel Functions](https://vercel.com/docs/functions/runtimes/container), while continuous queue consumers may fit [Vercel Daemons](https://vercel.com/docs/daemons).

Your databases, buckets, queues, and Cognito user pools can stay in AWS. Configure network access and authentication for each dependency before moving the compute. Vercel OIDC federation lets server-side code assume an IAM role for AWS API calls without storing AWS access keys; database authentication and Cognito sign-in still need their own configuration.

## Should your app move?

Assess each process separately. HTTP services must tolerate scaling to zero, store durable state externally, and fit [Function request and execution limits](https://vercel.com/docs/functions/limitations).

### Size and request limits

Pro and Enterprise Functions support up to 4 GB of memory and 2 vCPUs. Larger services need a different execution plan and may need application changes to divide work across invocations. Continuous workers have separate [Daemon limits](https://vercel.com/docs/daemons/limits).

[VCR image limits](https://vercel.com/docs/container-registry/limits-and-pricing) are 15 GB total and 2 GB per compressed layer. Check large ML or Chromium layers before migrating the build.

Vercel Functions limit request and response bodies to 4.5 MB. For larger uploads, have the client send the file directly to S3 with a presigned URL.

### Reachability

Container functions reach public AWS API endpoints outside your VPC. Check S3, SQS, DynamoDB, and other dependencies for policies tied to the old VPC, endpoint, or source IP. Test the public API path with the role Vercel will assume.

Private RDS and ElastiCache endpoints need a supported network path. Custom container functions cannot use Static IPs or Secure Compute, so resolve any fixed-IP or private-network requirement before deploying. The networking options below explain when to use a standard runtime or retain the calling service in AWS.

### State and background work

HTTP containers run as Vercel Functions and can stop between requests. Store durable data outside the instance. An application that depends on an EFS mount or unrecoverable in-memory state needs a storage redesign before it moves.

Continuous queue consumers can run on [Vercel Daemons](https://vercel.com/docs/daemons). Daemons keep a configured number of instances running but have no inbound HTTP or TCP endpoint and no persistent local storage. For work that pauses between steps or waits for approvals, use [Vercel Workflows](https://vercel.com/docs/workflows).

## How do AWS concepts map to Vercel?

Use this mapping to identify which configuration you can carry over and which parts need a separate migration decision.

| AWS component                                         | Vercel path                                                                                                               | Migration note                                                                                    |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Next.js on ECS, EKS, or EC2                           | [Next.js integration](https://vercel.com/docs/frameworks/full-stack/nextjs)                                               | Deploy the application source without its AWS container wrapper.                                  |
| HTTP container task                                   | [Container-based Function](https://vercel.com/docs/functions/runtimes/container)                                          | Vercel builds the image from source and scales request-serving instances automatically.           |
| Continuous queue consumer                             | [Daemon](https://vercel.com/docs/daemons)                                                                                 | Configure regional instance counts. Check queue access, shutdown, and retry behavior separately.  |
| ECS scheduled task                                    | [Cron-triggered HTTP handler](https://vercel.com/docs/cron-jobs) when the work fits Function limits                       | Cron does not preserve a task's process lifecycle or completion tracking.                         |
| Task definition CPU, memory, environment, and command | Function settings, environment variables, and image startup command                                                       | Carry over the intended values within the destination runtime's limits.                           |
| ECS services and ALB path routing                     | [Services](https://vercel.com/docs/services) and top-level rewrites                                                       | Services in one project deploy together. Add public routes only where needed.                     |
| CloudFront and AWS WAF                                | Vercel CDN and Firewall                                                                                                   | Recreate caching, redirects, and protection rules; AWS configuration is not imported.             |
| Route 53                                              | Existing DNS provider plus Vercel domain settings                                                                         | Keep the zone in Route 53 and change the traffic records during cutover.                          |
| NAT gateway with fixed egress                         | No equivalent for custom container functions                                                                              | Standard runtimes may use Static IPs or Secure Compute when their requirements fit.               |
| RDS and ElastiCache                                   | Retained AWS services                                                                                                     | Verify reachability and database authentication separately. Private endpoints remain private.     |
| S3, SQS, and DynamoDB                                 | Retained AWS APIs                                                                                                         | Use an assumed IAM role with the required operation and resource permissions.                     |
| Cognito and ALB authentication                        | Retained user pool plus application sign-in                                                                               | Reimplement ALB-owned sign-in if the load balancer previously supplied it.                        |
| EFS and other mounted volumes                         | External storage APIs or a persistence redesign                                                                           | Mounted filesystems do not move with the image.                                                   |
| ECS task role                                         | Separate IAM role for Vercel OIDC federation                                                                              | Preserve the ECS role while the AWS deployment remains available for rollback.                    |
| CloudWatch Logs, metrics, and X-Ray                   | [Runtime logs](https://vercel.com/docs/logs/runtime), [Observability](https://vercel.com/docs/observability), and tracing | Recreate the signals and alerts you rely on; compare coverage before retiring AWS monitoring.     |
| ECR and build pipeline                                | [Vercel Container Registry](https://vercel.com/docs/container-registry) and source deployments                            | Vercel builds and pushes the deployment image. Keep the AWS pipeline available during validation. |
| ECS deployment and rollback                           | Vercel deployments plus a traffic and worker handoff                                                                      | The first project deployment uses Production; DNS rollback does not undo data writes.             |

## Before you begin

Have the application source, AWS task or instance configuration, IAM policies, and required secret values available. Record startup-command overrides, volumes, scheduled jobs, and any authentication handled by the load balancer.

Install the latest [Vercel CLI](https://vercel.com/docs/cli) and ensure you can access the destination team. Local container testing also needs the Docker CLI and a running Docker daemon. From the repository root, sign in and [link the directory to a Vercel project](https://vercel.com/docs/cli/link):

```bash
vercel login
vercel link
```

Keep AWS serving traffic while you prepare and test the replacement.

### Choose a network path to retained services

Credentials do not make a private endpoint reachable. Choose an approved network path for every dependency before moving the process that calls it.

#### Public database endpoint

Direct public RDS access requires `PubliclyAccessible`, public subnets with an internet gateway route, enabled VPC DNS settings, and network rules that admit the connection. Check all four; enabling public access alone is insufficient.

Custom container functions do not provide a fixed egress IP. If your database requires a narrow source-IP allowlist, select a supported networking option or keep the calling service in AWS. Opening a database port to `0.0.0.0/0` admits connection attempts from any IPv4 address and is a separate security decision, not a requirement of OIDC federation.

For an approved public endpoint, use TLS with certificate verification and the database's supported authentication method. IAM database authentication controls the application's login but does not make a private endpoint reachable or remove other database accounts. ElastiCache remains a private-network dependency.

#### Standard runtime with supported networking

If the service can run on a supported standard runtime, moving it without the Dockerfile may make Vercel networking options available. [Static IPs](https://vercel.com/docs/networking/static-ips) provides fixed outbound addresses for allowlisting on Pro and Enterprise. Pro pricing is $100 per project per month plus Private Data Transfer.

For private AWS VPC connectivity, [Secure Compute](https://vercel.com/docs/networking/secure-compute) supports Node.js, Python, Ruby, and Go Functions on Enterprise. Neither option supports custom container images. Check that the native runtime can run your dependencies before choosing this route; otherwise, retain the service in AWS.

#### Retain the calling service in AWS

Keep a process on its current AWS runtime when its network or system requirements cannot move. Other parts of the app can migrate if they have an authenticated, reachable interface to that process. Preserve its deployment pipeline and monitoring alongside the AWS data services it still uses.

### Preserve application sign-in

Check whether your application authenticates users itself or relies on an ALB `authenticate-cognito` or `authenticate-oidc` listener action. If the ALB owns that flow, implement and test sign-in in the new application path before sending users to it. Do not continue trusting identity headers that any client can supply after the ALB is removed.

Keep the Cognito user pool if you still use it, and configure the app client's callback and sign-out URLs for the hostnames you will test and serve. Verify sign-in, sign-out, and denial of protected routes for a signed-out user. The Vercel OIDC role used for AWS API calls does not provide this application authentication.

### Check compliance and data location

Review the [Vercel Trust Center](https://security.vercel.com/) for the reports and agreements your workload requires. Vercel has a SOC 2 Type 2 attestation and ISO 27001:2022 certification; HIPAA support requires an eligible plan and a Business Associate Agreement.

Check the [shared compliance responsibilities](https://vercel.com/docs/security/compliance) for the application and its retained AWS services. Function region settings do not cover every data flow: review storage, logs, caching, and Routing Middleware against your residency requirements.

## Migrate your AWS app in 8 steps

### 1\. Choose the framework or container path

Deploy a standard Next.js app through the [Next.js integration](https://vercel.com/docs/frameworks/full-stack/nextjs) without a Dockerfile. Other [supported frameworks](https://vercel.com/docs/frameworks) can also deploy directly. Use the container path for HTTP services that need their existing image or system dependencies.

For a [custom Next.js server](https://nextjs.org/docs/app/guides/custom-server), first check whether Route Handlers or framework routing can replace the custom behavior. If you retain the server in a container, validate its routing and caching against the container runtime before cutover.

For a single HTTP container, copy your existing Dockerfile to `Dockerfile.vercel` at the project root. Vercel detects the file, builds the image, pushes it to VCR, and adds a rewrite for incoming traffic. For a project with multiple services, use the per-service paths in step 2.

This example assumes a Node.js service with a lockfile and a `server.js` entrypoint. Retain any compilation stages your app needs, and use `.dockerignore` to exclude local secrets and development files from the build context.

```docker
FROM node:lts-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .

# The server must listen on $PORT
CMD ["node", "server.js"]
```

Carry over any startup command or entrypoint that ECS overrides outside the Dockerfile. Check system dependencies, mounted files, and runtime limits before assuming the image can run unchanged.

#### Configure the HTTP container

Your server must listen on `0.0.0.0` at the port expected by Vercel. The default is `80`; set `PORT` in project [environment variables](https://vercel.com/docs/environment-variables) if the service uses another port, and make the server read that value at startup.

HTTP instances scale down after 5 minutes without traffic in Production and 30 seconds in Preview. Handle `SIGTERM` and complete cleanup within the 30-second grace period before forced termination. Store unfinished work where another instance can recover it.

### 2\. Configure multiple services when needed

Skip this step for a single application. [Services](https://vercel.com/docs/services) let a project combine a native Next.js app with independent HTTP containers.

One project deploys and rolls back as a unit, so a change to any service ships all of them together. That is the tradeoff for a shared route table, private service-to-service bindings, and one firewall and domain config for everything. If a service needs its own deploy and rollback cadence, keep it in its own project.

Each service has a `root` relative to `vercel.json`. This example deploys Next.js from `apps/web/` and an HTTP search service from `services/search/Dockerfile.vercel`. Save the complete configuration as `vercel.json` at the repository root:

```json
{
  "services": {
    "web": {
      "root": "apps/web/",
      "framework": "nextjs",
      "bindings": [
        {
          "type": "service",
          "service": "search",
          "format": "url",
          "env": "SEARCH_URL"
        }
      ]
    },
    "search": {
      "root": "services/search/",
      "entrypoint": "Dockerfile.vercel"
    }
  },
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": {
        "service": "web"
      }
    }
  ]
}
```

Only `web` has a public rewrite. Its binding grants access to `search` and supplies `SEARCH_URL` to server-side code. Keep build and runtime settings such as `buildCommand`, `framework`, and `functions` inside their owning service. The [service configuration reference](https://vercel.com/docs/services/config-reference) lists the supported fields.

Call the search service from a server-side handler in `web`. This example assumes it serves a `search` path that returns JSON:

```javascript
export async function searchProducts(query) {
  const baseUrl = process.env.SEARCH_URL;
  if (!baseUrl) throw new Error('SEARCH_URL is not configured');

  const url = new URL('search', baseUrl);
  url.searchParams.set('q', query);
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Search service returned ${response.status}`);
  }
  return response.json();
}
```

The binding points to the matching deployment, so preview services call other services in that preview. [Bindings](https://vercel.com/docs/services/bindings) are available at runtime, not during builds or in Routing Middleware. Use Daemons separately for continuously polling workers.

#### Configure public protection and internal authorization

Public requests enter through the project's route table, where Vercel Firewall, Deployment Protection, and redirects apply. Configure them once for the project.

Internal binding calls skip that public pipeline and make the target reachable. Your application must still authorize each requested action.

### 3\. Restore AWS access and application configuration

[Vercel OIDC federation](https://vercel.com/docs/oidc/aws) lets your backend assume an IAM role without a stored AWS access key. The role's trust policy controls which Vercel project and environment may assume it. Its permissions policy controls which AWS operations the resulting credentials may perform.

Create a dedicated role for Vercel and retain the existing ECS task role for the AWS deployment. Configure a separate, narrowly scoped role for previews when they need AWS access.

#### Register Vercel as an OpenID Connect identity provider in IAM

In the AWS console, open IAM, then Identity Providers, then Add Provider, and select OpenID Connect. The provider URL is `https://oidc.vercel.com/[TEAM_SLUG]` in Team mode and `https://oidc.vercel.com` in Global mode. Enter `https://vercel.com/[TEAM_SLUG]` in the Audience field. See the [OIDC federation docs](https://vercel.com/docs/oidc) for Global mode, alternate audiences, and the CLI equivalent.

#### Scope the trust policy to one project and one environment

The trust policy controls who can assume the role through `sts:AssumeRoleWithWebIdentity`. Use condition keys to restrict that access to one project and environment.

The following policy uses Team issuer mode. Replace each placeholder with your account, team, and project values. If you use Global mode, follow the [matching issuer and condition-key format](https://vercel.com/docs/oidc/aws) instead.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::[AWS_ACCOUNT_ID]:oidc-provider/oidc.vercel.com/[TEAM_SLUG]"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.vercel.com/[TEAM_SLUG]:sub": "owner:[TEAM_SLUG]:project:[PROJECT_NAME]:environment:production",
          "oidc.vercel.com/[TEAM_SLUG]:aud": "https://vercel.com/[TEAM_SLUG]"
        }
      }
    }
  ]
}
```

For previews, use the subject `owner:[TEAM_SLUG]:project:[PROJECT_NAME]:environment:preview` in a separate role's trust policy. Limit its permissions and prefer non-production resources.

#### Grant access to the required AWS resources

Attach a permissions policy for the operations your application needs. The following example permits the S3 listing and RDS connection shown below. Replace every placeholder and use the database resource ID, not its display name, in the RDS ARN. Omit either statement if the application does not use that service.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME"
    },
    {
      "Effect": "Allow",
      "Action": "rds-db:connect",
      "Resource": "arn:aws:rds-db:YOUR_REGION:YOUR_ACCOUNT_ID:dbuser:YOUR_DB_RESOURCE_ID/YOUR_DB_USERNAME"
    }
  ]
}
```

Set `AWS_ROLE_ARN` in the matching Vercel environment to this role's ARN. Add only the required SQS, DynamoDB, or Secrets Manager permissions for other calls. Existing resource policies and organization restrictions still apply.

#### Install the AWS clients

The `awsCredentialsProvider` function supplies the AWS SDK with temporary credentials backed by Vercel OIDC. Install the clients for the examples you use:

```bash
npm i @aws-sdk/client-s3 @aws-sdk/rds-signer pg @vercel/oidc-aws-credentials-provider
```

TypeScript projects using the PostgreSQL example also need its type declarations:

```bash
npm i -D @types/pg
```

#### Read from S3

Set `S3_BUCKET_NAME`, `AWS_ROLE_ARN`, and `AWS_REGION` for the environment you are testing. Save the following as a server-only helper such as `lib/aws-s3.ts` and call it from application code after checking the caller's access to the bucket contents. Vercel OIDC supplies AWS credentials to the server; your application still decides which users may receive the result.

```typescript
import * as S3 from '@aws-sdk/client-s3';
import { awsCredentialsProvider } from '@vercel/oidc-aws-credentials-provider';

const s3 = new S3.S3Client({
  region: process.env.AWS_REGION!,
  credentials: awsCredentialsProvider({
    roleArn: process.env.AWS_ROLE_ARN!,
  }),
});

export async function listBucketKeys() {
  const result = await s3.send(
    new S3.ListObjectsV2Command({ Bucket: process.env.S3_BUCKET_NAME! }),
  );
  return result.Contents?.map((object) => object.Key) ?? [];
}
```

This helper returns object keys from one listing response. Use the SDK's pagination support if the application needs every key in a larger bucket.

#### Connect to RDS PostgreSQL

For PostgreSQL, enable IAM database authentication on a supported RDS instance and create a dedicated application database user. Grant that user `rds_iam` and the SQL permissions it needs. IAM authentication takes precedence over password authentication for this user, so keep an existing password-based account available if the AWS deployment still uses it. The IAM role also needs `rds-db:connect` for this database resource ID and username.

Once the database is reachable, set `RDS_HOSTNAME`, `RDS_PORT`, `RDS_DATABASE`, and `RDS_USERNAME`. Use the RDS endpoint rather than a custom DNS alias. Set `RDS_CA_BUNDLE` to the PEM contents of its [RDS certificate bundle](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html).

Save this server-only helper as `lib/aws-db.ts` and import `pool` from your database code. The password callback generates a token when the pool opens a connection. Token expiry does not terminate an established session.

```typescript
import { Signer } from '@aws-sdk/rds-signer';
import { awsCredentialsProvider } from '@vercel/oidc-aws-credentials-provider';
import { Pool } from 'pg';

function requiredEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing environment variable: ${name}`);
  return value;
}

const host = requiredEnv('RDS_HOSTNAME');
const user = requiredEnv('RDS_USERNAME');
const port = Number(requiredEnv('RDS_PORT'));

const signer = new Signer({
  hostname: host,
  username: user,
  port,
  region: requiredEnv('AWS_REGION'),
  credentials: awsCredentialsProvider({
    roleArn: requiredEnv('AWS_ROLE_ARN'),
  }),
});

export const pool = new Pool({
  host,
  user,
  port,
  database: requiredEnv('RDS_DATABASE'),
  password: () => signer.getAuthToken(),
  ssl: {
    ca: requiredEnv('RDS_CA_BUNDLE'),
    rejectUnauthorized: true,
  },
  max: 2,
  connectionTimeoutMillis: 5000,
  idleTimeoutMillis: 5000,
});

pool.on('error', (error) => {
  console.error('Idle database connection failed', error);
});
```

The example allows two connections per instance. Budget connections across Vercel instances, services, and AWS during cutover. Test concurrent load; one pool's limit does not cap total connections.

RDS Proxy can manage database connections, but its endpoint remains private. It needs a supported private network path and does not make a database reachable from a public container function.

#### Set AWS and Function regions

Set `AWS_REGION` explicitly for the AWS clients in these examples. Vercel otherwise supplies the Function's execution region, which may differ from the region containing the resource. If dependencies span AWS regions, configure each SDK client or signer for its target resource.

Choose nearby [Function regions](https://vercel.com/docs/functions/configuring-functions/region) in project settings or with `regions` in `vercel.json`. New projects default to `iad1`, near AWS `us-east-1`.

European regions include `cdg1`, `dub1`, `fra1`, and `lhr1`. Hobby supports one region, Pro up to five, and Enterprise all available regions.

Routing Middleware runs globally by default and does not inherit Function region settings. Put processing that requires a specific region inside Functions configured for that location.

#### Configure application secrets

You can copy application secrets from Secrets Manager or Parameter Store into Vercel Secret variables, or fetch them at runtime through the assumed role. Copied values need an explicit rotation process that updates Vercel and creates a new deployment. Runtime retrieval keeps AWS as the source of truth; grant access to the required secrets and use a cache refresh policy that accounts for rotation.

Keep the existing deployment's credentials available during validation. Retire unused keys only after the cutover checks in step 8.

### 4\. Plan scheduled jobs and continuous workers

#### Scheduled tasks

ECS scheduled tasks launch container processes. Use [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs) when the work can run through an HTTP GET handler within Function limits. Keep incompatible batch jobs in AWS or redesign their execution.

Declare eligible schedules with `crons` in `vercel.json` and check the plan's schedule limits. Cron invokes the production deployment in UTC. Set `CRON_SECRET` and [validate the Authorization header](https://vercel.com/docs/cron-jobs/manage-cron-jobs#securing-cron-jobs) before starting work.

Cron does not retry failed invocations. Make the handler safe to repeat, prevent unwanted overlap, and recover work missed since the last successful run. Keep Cron disabled until you stop the corresponding AWS schedule.

#### Continuous queue consumers

Continuous consumers can use [Vercel Daemons](https://vercel.com/docs/daemons) when dependencies are reachable and no inbound HTTP or TCP endpoint is needed. During the beta, Daemons run only in Production with ephemeral local storage and configured instance counts.

Put the worker source and Dockerfile in `worker/`. Its startup command must run the consumer. Merge this `daemons` property into your existing `vercel.json`; preserve any `services`, `rewrites`, and other settings:

```json
{
  "daemons": {
    "worker": {
      "root": "worker/",
      "entrypoint": "Dockerfile",
      "instances": { "iad1": 0 }
    }
  }
}
```

The zero count keeps the worker stopped when no scaling override exists. Check [dashboard overrides](https://vercel.com/docs/daemons/managing-daemons) and the running count before deployment.

Test the image with Docker and development credentials because `vercel dev` does not start Daemons.

Keep the existing queue backend, such as SQS, and grant the worker only its required permissions. For AWS federation, follow the [Daemon OIDC guidance](https://vercel.com/docs/daemons#environment-variables-and-oidc): a long-lived process must use the refreshed token file or a supported SDK instead of caching its startup token.

At handoff, stop the AWS consumer from taking new work and drain its active jobs before starting Vercel instances. Handle `SIGTERM` and make interrupted jobs safe to retry, since versions can overlap during later rollouts. See the [Daemon migration guide](https://vercel.com/kb/guide/vercel-daemons) for the full lifecycle and configuration.

### 5\. Test locally

Run `vercel dev` from the linked project with Docker available for container services. Use development credentials or a separately scoped development federation role; the production trust policy above does not admit local development.

Test application routes, authentication, AWS API calls, and database connections. Confirm the configured HTTP port and test shutdown handling. Exercise concurrent database requests to check the pool budget before using production traffic.

For Daemons, use local Docker testing with a development queue. Keep Vercel's production consumers and Cron schedules stopped until their planned handoff. Leave the AWS workers and schedules running until then.

### 6\. Deploy and verify the application

Before deploying, finish the environment and AWS access configuration in step 3. The [first deployment of a new project](https://vercel.com/docs/deployments/environments#first-deployment) uses Production even when you omit `--prod`. Variables scoped only to Preview are unavailable to that build.

Keep the AWS domain serving traffic during testing. Leave [Cron jobs disabled](https://vercel.com/docs/cron-jobs/manage-cron-jobs#cron-jobs-maintenance) and [Daemons stopped](https://vercel.com/docs/daemons/managing-daemons#stopping-a-daemon) until their handoff. Deploy with the [Vercel CLI](https://vercel.com/docs/cli):

```bash
vercel deploy
```

After the first deployment, non-production branches and CLI deployments without `--prod` normally create previews. Validate the generated deployment URL before moving the production domain.

For container deployments, Vercel builds and pushes the image to `vcr.vercel.com/<team-slug>/<project-slug>/<repository>:<tag>`. Manual image pushes are a separate operation: `vercel vcr login docker` authenticates a linked project for 12 hours.

Verify signed-in and signed-out routes, AWS access, uploads, caching, and response times on the generated URL. Confirm that protected data remains inaccessible to unauthorized callers.

If you made fixes in previews, deploy the intended revision to Production with `vercel deploy --prod` and repeat the checks there. Keep Vercel background work stopped until step 7 and cut over only to the verified Production deployment.

#### Prepare logs and monitoring

Container `stdout` and `stderr` are broadcast to in-flight requests on the instance. Include a request ID in your application logs so you can correlate each operation with its caller.

Check [runtime log retention](https://vercel.com/docs/logs/runtime) before cutover. Base retention is 1 day on Pro and 3 days on Enterprise; Observability Plus extends either to 30 days. Use a [log drain](https://vercel.com/docs/drains) when you need to retain logs in your existing system. Drains are available on Pro and Enterprise and are billed by volume.

### 7\. Switch traffic and background work

Prepare certificates and validate the production hostname before changing its traffic records. Keep Route 53 as the DNS provider during this migration.

#### Lower editable DNS TTLs before cutover

Inspect the records that currently route each hostname to AWS. Route 53 alias records pointing to an ALB use the load balancer's 60-second TTL; you cannot edit that TTL. This applies to aliases at both the apex and subdomains.

Reduce editable TTLs to your cutover value, such as 60 seconds, and wait out the previous TTL before changing the destination. Keep that value through validation and the rollback window before restoring your normal TTL.

#### Verify HTTPS and change the traffic records

1. Exercise the generated deployment URL. Test application routes, sign-in, and the AWS operations the application needs before it serves customer traffic.
   
2. Add every production hostname you use in **Domains**, including the apex and `www` when applicable. Record the DNS values Vercel provides. Keep Route 53 as the DNS provider during cutover.
   
3. Check existing CAA restrictions and allow Let's Encrypt where required. [Pre-generate the certificates](https://vercel.com/docs/domains/pre-generating-ssl-certs) for the production hostnames, add the requested TXT challenges in Route 53, and wait for successful issuance.
   
4. Follow the [certificate verification procedure](https://vercel.com/kb/guide/zero-downtime-migration) to test HTTPS for the actual production hostname against Vercel before changing its traffic record. Confirm redirects, cookies, and authentication callbacks with that hostname.
   
5. Update the production records to the values shown in Vercel's **Domains** settings. Leave the AWS service and Vercel deployment available during DNS propagation.
   
6. Monitor Vercel errors and latency alongside the ALB's `RequestCount` and access logs. Missing ALB datapoints alone do not prove that all traffic has moved, and CloudWatch alarm states depend on their missing-data configuration.
   
7. Update callers that still use an AWS-generated hostname. Coordinate scheduled jobs and queue consumers separately from DNS so the two platforms do not perform the same work unintentionally.
   

After stopping the corresponding AWS schedules, enable [Cron Jobs in project settings](https://vercel.com/docs/cron-jobs/manage-cron-jobs#cron-jobs-maintenance) and verify the next run. Retain a record of which system owns each schedule for rollback.

Once the AWS consumer has drained, start the staged Daemon by setting its intended regional count in the dashboard or deploying an updated `instances` value to Production. Resolve any scaling override, confirm the running count, and verify queue processing and acknowledgments.

### 8\. Preserve rollback and retire unused resources

Record the existing DNS values and retain the healthy AWS deployment through an agreed rollback window. Keep its IAM permissions, secrets, target groups, and load balancer available. Any database schema changes made during migration must remain compatible with both versions.

If validation fails, restore the DNS records that routed traffic to AWS. Leave Vercel running while cached records expire and monitor requests reaching both deployments. Reverting DNS changes the destination of future requests; it does not undo database writes. Reverse the scheduler or worker handoff separately and account for jobs already processed.

After cutover, wait for the previous DNS TTL and agreed rollback window to pass. Use metrics and logs to confirm that user traffic, scheduled work, and direct callers no longer need the old compute before retiring it. Retain the AWS data services and shared infrastructure still in use.

#### Retire unused access keys

After verifying the deployment and its rollback path, inventory long-lived AWS access keys used by the app or build pipeline. Check last-used information and other consumers before deactivating a key. Monitor for failures, then delete keys that are no longer needed.

Retain the AWS deployment's credentials through its rollback window. Apps using ECS task roles may have no stored access keys to retire.

## Estimate the operating cost

HTTP containers use [Active CPU billing](https://vercel.com/docs/functions/usage-and-pricing#active-cpu). CPU time accrues while code executes and pauses while a request waits for an RDS query, an S3 read, or an upstream API.

Provisioned memory is billed while the instance handles requests, from the first request until the last in-flight request completes. Neither CPU nor memory is billed between requests, including the idle period before the HTTP instance scales down.

| Item               | Rate                           |
| ------------------ | ------------------------------ |
| Active CPU         | $0.128 to $0.221 per CPU-hour  |
| Provisioned memory | $0.0106 to $0.0183 per GB-hour |
| Invocations on Pro | $0.60 per million              |
| VCR storage        | $0.10 per GB                   |

CPU and memory rates vary by region. Use the current [Function pricing](https://vercel.com/docs/functions/usage-and-pricing) and [VCR pricing](https://vercel.com/docs/container-registry/limits-and-pricing) for your estimate. Include delivery, retained AWS services, transfer costs, and any optional networking or observability products.

Estimate continuous workers from their measured usage and configured instance counts. [Daemons stay running between jobs](https://vercel.com/docs/daemons), so HTTP scale-to-zero assumptions do not apply to them.

## Troubleshooting

Start with the deployment or runtime error, then check the matching layer below.

| Symptom                                         | Check                                                                                          | Next step                                                                                                       |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Build succeeds, requests time out               | The server may be listening on the wrong interface or port                                     | Listen on `0.0.0.0` at the configured port; check startup logs and dependencies too                             |
| Container killed mid-request                    | The request passed the function duration limit, or cleanup exceeded the `SIGTERM` grace period | Check termination and timeout logs; keep Function cleanup within 30 seconds and requests within duration limits |
| AWS role assumption is denied                   | The OIDC issuer, audience, subject, or role trust policy does not match                        | Verify the identity provider and the exact project/environment conditions in the trust policy                   |
| An AWS service API returns `AccessDenied`       | The assumed role lacks an action or a policy denies it                                         | Check the operation's required permissions, resource policies, and organization restrictions                    |
| RDS connection times out or is refused          | The selected network path does not admit the connection                                        | Check the endpoint, port, routing, and security rules for the selected public or private connection             |
| RDS reports a TLS or authentication failure     | The certificate, IAM database permission, database user, or token settings do not match        | Verify the RDS CA bundle, `rds-db:connect` resource, IAM-enabled database user, and signer hostname/region      |
| AWS calls reach the wrong region                | `AWS_REGION` defaults to the function’s execution region                                       | Pin `AWS_REGION` as a project environment variable                                                              |
| An expected public service returns 404          | Check public rewrites and the application route                                                | Add a service rewrite only if it should receive public traffic; keep internal services behind bindings          |
| Log lines interleave across concurrent requests | Container `stdout` and `stderr` are broadcast to all inflight requests on the instance         | Thread a request ID through a request-scoped logger                                                             |
| First request after an idle period is slow      | The function scaled to zero after 5 minutes in production or 30 seconds in preview             | Check initialization time and dependency setup after scale-to-zero; a warm instance is not guaranteed           |
| Image push to VCR is rejected                   | Check the registry error, project scope, token expiry, and image limits                        | Reauthenticate if the token expired; verify permissions and image size for other rejections                     |

## What needs a separate migration plan?

- OpenNext, SST, and AWS Amplify need a source and framework migration plan. Their existing deployment artifacts do not map directly to this Dockerfile procedure.
  
- Kubernetes operators, custom resources, service meshes, and cluster-level scheduling require an architecture review. An independent HTTP container from EKS may still fit the Function path.
  
- Workloads that depend on mounted persistent volumes or state that cannot recover after a restart need a persistence redesign. Continuous workers may fit Daemons when their dependencies and networking are compatible.
  

## Related resources

- [Deploy a Docker app on Vercel](https://vercel.com/kb/guide/docker)
  
- [Configure multiple services](https://vercel.com/kb/guide/vercel-services)
  
- [Run continuous workers with Vercel Daemons](https://vercel.com/kb/guide/vercel-daemons)
  
- [Migrate from Google Cloud Run](https://vercel.com/kb/guide/migrate-cloud-run-to-vercel), [Fly.io](https://vercel.com/kb/guide/migrate-fly-io-to-vercel), or [Render](https://vercel.com/kb/guide/migrate-render-to-vercel)
  
- [Container runtime reference](https://vercel.com/docs/functions/runtimes/container)
  

## FAQ

### Can Vercel run a container that is not a Next.js app?

Yes. Vercel can run HTTP containers built with Go, Rails, Spring Boot, Express, Laravel, ASP.NET, FastAPI, nginx, and other stacks. The server must listen on the configured port and fit Function limits, storage behavior, and networking requirements. Continuous consumers use the separate Daemons path.

### What happens to my AWS bill?

HTTP compute moves from AWS instance or task charges to Vercel's CPU, memory, and invocation charges, while delivery moves to Vercel's CDN pricing. Compare measured usage; lower cost depends on utilization and the services you choose.

Retained AWS services still incur storage, request, and other usage charges. Estimate transfer costs for each service, region, and network path, then verify them during testing. Moving compute does not put every S3 or database read into the same transfer category.

Vercel waives CDN Requests and Fast Data Transfer for traffic blocked by DDoS mitigation or denied, challenged, or rate-limited by its WAF. Requests served before mitigation and other accepted traffic can still incur charges. Include Daemons and optional products separately if you use them.

## Related Resources

- [Backends on Vercel: Python, containers, and Daemons](https://vercel.com/blog/backends-on-vercel-python-containers-and-daemons): Explore how Python, container images, and background workers fit into the Vercel platform.
- [The Complete Guide to Vercel Services](https://vercel.com/kb/guide/vercel-services): Vercel Services let you deploy multiple frontends and backends in one project on a shared domain. Learn how to define services in vercel.json, route requests by path prefix, and connect services with generated environment variables.
- [Running Docker on Vercel](https://vercel.com/kb/guide/docker): Learn how to run Docker on Vercel by deploying OCI container images as Vercel Functions, storing them in Vercel Container Registry (VCR), and running containers in Vercel Sandbox.

## More Fluid compute guides

- [How to choose between Functions, containers, and Daemons on Vercel](/kb/guide/vercel-functions-containers-daemons): Choose Vercel Functions for HTTP requests, container images for custom runtimes, and Daemons for continuous background workers.
- [Next.js on Vercel vs Webflow Cloud](/kb/guide/next-js-on-vercel-vs-webflow-cloud): Compare running Next.js on Vercel Functions with Fluid compute against Webflow Cloud on Cloudflare Workers. Learn how Next.js feature support, runtimes, storage, pricing, and developer experience differ so you can choose the right platform.
- [Migrate a Next.js app from Webflow Cloud to Vercel](/kb/guide/migrate-a-next-js-app-from-webflow-cloud-to-vercel): Move your Next.js app from Webflow Cloud to Vercel: remove the OpenNext Cloudflare adapter, drop the base path, map storage bindings to Vercel Blob, Redis, and Postgres, and deploy with Git or the vercel CLI.