---
title: Deploy Go apps on Vercel using Docker
description: Deploy an existing Dockerized Go app to Vercel using Memos as a real-world example, with Neon Postgres for durable data.
url: /kb/guide/deploy-go-using-docker-vercel
canonical_url: "https://vercel.com/kb/guide/deploy-go-using-docker-vercel"
published: 2026-07-21
last_updated: 2026-07-28
authors: Anshuman Bhardwaj
related:
  - /docs/functions/runtimes/go
  - /docs/container-registry
  - /docs/services
  - /docs/functions/container-images
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Go apps usually deploy cleanly on Vercel with the native Go preset: a `go.mod`, a supported entrypoint, and an HTTP server that listens on `PORT`. But not every Go project fits that shape. Some ship their own startup scripts, depend on external binaries, or are already published as a container image you'd rather run as-is.

Docker often fits those cases better. This guide shows how to deploy a Dockerized Go application on Vercel using [Memos](https://usememos.com/docs/deploy/docker), an open-source note-taking app, as an example. Memos runs as a Go server from the `neosmemo/memos:stable` image and stores durable data in Postgres.

## When to run Go using Docker

Use Vercel's native [Go Runtime](https://vercel.com/docs/functions/runtimes/go) when your app is a standard Go HTTP server with a root `go.mod`, a supported entrypoint like `main.go`, and code you can adapt to listen on `PORT`.

Use Docker when the app:

- Already ships as a maintained OCI image.
  
- Has a custom entrypoint or runtime behavior you do not want to rewrite.
  
- Needs operating-system packages, CA bundles, native binaries, or bundled assets.
  
- Uses a framework or app layout outside Vercel's Go preset assumptions.
  
- Should run locally and in production from the same image.
  
- Is an upstream open-source app, like Memos, where you want to configure rather than fork.
  

Docker on Vercel still runs as a Vercel Function. The container must serve HTTP, stay stateless, use external storage for durable data, and respect Function limits. Vercel builds the image, stores it in [Vercel Container Registry](https://vercel.com/docs/container-registry), and routes traffic to it.

## How to run Docker on Vercel

Vercel supports container images through `Dockerfile.vercel` or `Containerfile.vercel`. In a Services project, declare the container service in `vercel.json` and expose it with a rewrite.

Memos is a useful example because it is a Go app, already has an official Docker image, and can switch from local SQLite to Postgres through environment variables.

### Add Vercel Dockerfile

First, create a `Dockerfile.vercel` that only runs the upstream Memos image on a port that Vercel can reach:

```dockerfile
FROM neosmemo/memos:stable

ENV MEMOS_ADDR=0.0.0.0 \
    MEMOS_PORT=5230 \
    MEMOS_DATA=/tmp/memos

RUN mkdir -p /tmp/memos && chmod 1777 /tmp/memos

EXPOSE 5230
```

Here is what each part does:

- `FROM neosmemo/memos:stable` uses the official Memos image instead of rebuilding the Go app from source.
  
- `MEMOS_ADDR=0.0.0.0` makes the server accept traffic from outside the container.
  
- `MEMOS_PORT=5230` keeps Memos on its normal unprivileged port. The image runs as a non-root user, so binding to port `80` can fail.
  
- `MEMOS_DATA=/tmp/memos` gives Memos a writable local directory. On Vercel this is ephemeral, so it must not be the system of record.
  
- `EXPOSE 5230` documents the container port.
  

Also, set the Vercel project environment variable `PORT=5230`. Vercel routes to port `80` by default unless `PORT` is configured. Memos itself reads `MEMOS_PORT`, while Vercel's container router reads `PORT`, so both values matter.

```bash
vercel env add PORT production --no-sensitive
vercel env add PORT preview --no-sensitive
vercel env add PORT development
```

### Add Postgres from the Vercel Marketplace

The Memos app requires a persistent database service. SQLite is not suitable for durable production data on Vercel Functions. Use a Postgres database integration such as Neon instead. This will provide the data and file storage for the app.

From a linked Vercel project, you can discover and install Neon from the Marketplace with the Vercel CLI.

Install Neon, create a resource, and connect it to the current project:

```bash
vercel integration add neon
```

### Connect Memos to Postgres

Memos needs these settings to use Postgres:

```bash
MEMOS_DRIVER=postgres
```

Add `MEMOS_DRIVER=postgres` to the Dockerfile:

```dockerfile
ENV MEMOS_ADDR=0.0.0.0 \
    MEMOS_PORT=5230 \
    MEMOS_DRIVER=postgres \
    MEMOS_DATA=/tmp/memos
```

You could manually copy Neon's connection string into a new Vercel environment variable named `MEMOS_DSN`, but that duplicates a secret. To avoid duplicating the secret, derive `MEMOS_DSN` at startup.

Add this `ENTRYPOINT` line to `Dockerfile.vercel`:

```dockerfile
ENTRYPOINT ["sh", "-c", "export MEMOS_DSN=\"${MEMOS_DSN:-${DATABASE_URL_UNPOOLED:-$DATABASE_URL}}\"; for bin in /usr/local/memos/memos /usr/local/bin/memos /usr/bin/memos /memos; do if [ -x \"$bin\" ]; then exec \"$bin\"; fi; done; echo \"memos binary not found\" >&2; exit 127"]
```

This line does three jobs:

- If `MEMOS_DSN` is already set, Memos uses it.
  
- Otherwise, it maps Neon's `DATABASE_URL_UNPOOLED` to `MEMOS_DSN`, falling back to `DATABASE_URL`.
  
- It starts the Memos binary from the upstream image.
  

The final `Dockerfile.vercel` looks like this:

```dockerfile
FROM neosmemo/memos:stable

# Vercel's container router is configured with PORT=5230 for this project.
# Memos runs as a non-root user, so it cannot bind to privileged port 80.
# Memos stores durable app data in Neon. The startup command maps Neon-provided
# database URLs to Memos' MEMOS_DSN when MEMOS_DSN is not set directly.
ENV MEMOS_ADDR=0.0.0.0 \
    MEMOS_PORT=5230 \
    MEMOS_DRIVER=postgres \
    MEMOS_DATA=/tmp/memos

RUN mkdir -p /tmp/memos && chmod 1777 /tmp/memos

EXPOSE 5230

ENTRYPOINT ["sh", "-c", "export MEMOS_DSN=\"${MEMOS_DSN:-${DATABASE_URL_UNPOOLED:-$DATABASE_URL}}\"; for bin in /usr/local/memos/memos /usr/local/bin/memos /usr/bin/memos /memos; do if [ -x \"$bin\" ]; then exec \"$bin\"; fi; done; echo \"memos binary not found\" >&2; exit 127"]
```

### Add a Services config (optional)

[Vercel Services](https://vercel.com/docs/services) lets you deploy one or more services in a single Vercel project. If you later add a frontend, you can route only a path prefix to the Go container and send everything else to the frontend service:

```json
{
  "services": {
    "web": {
      "root": "apps/web",
      "framework": "nextjs"
    },
    "api": {
      "root": "apps/api",
      "runtime": "container",
      "entrypoint": "Dockerfile.vercel"
    }
  },
  "rewrites": [
    { "source": "/api/(.*)", "destination": { "service": "api" } },
    { "source": "/(.*)", "destination": { "service": "web" } }
  ]
}
```

### Deploy to Vercel

From the project root:

```bash
vercel deploy --prod
```

During deployment, Vercel builds the image, pushes it to Vercel Container Registry, creates a Function for the container service, and aliases the production URL.

After deploy, verify the app:

```bash
curl -I https://your-project.vercel.app
vercel inspect https://your-project.vercel.app
vercel logs https://your-project.vercel.app --since 10m --expand
```

## Troubleshooting

### The deployment is ready, but the site returns 404

**Cause**: The container built, but no public route points to it. In Services mode, a service is private until a top-level rewrite exposes it.

**Fix**: Add rewrites in the `vercel.json`:

```json
{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": { "service": "memos" }
    }
  ]
}
```

Also, confirm the project framework is set to Services when using `services` in `vercel.json`.

### The container returns 500

**Cause**: The app is trying to listen on port `80` while running as a non-root user. Memos runs as a non-root user, so binding to privileged ports can fail.

**Fix**: Set Memos to listen on an unprivileged port:

```dockerfile
ENV MEMOS_PORT=5230
EXPOSE 5230
```

Then set the Vercel project environment variable:

```bash
PORT=5230
```

Vercel uses `PORT` to decide where to route traffic inside the container. Memos uses `MEMOS_PORT` to decide where the app listens. You need both.

### Build logs say no `functions` or `static` directory

**Cause**: In the Services and container path, this warning can appear even when Vercel produced the container-backed Function.

**Fix**: Inspect the deployment instead of relying only on that warning. The app is deployed as a container image.

```bash
vercel inspect <deployment-url>
```

## Related resources

- [Vercel Container Images](https://vercel.com/docs/functions/container-images)
  
- [Vercel Services](https://vercel.com/docs/services)
  
- [Vercel Go Runtime](https://vercel.com/docs/functions/runtimes/go)
  
- [Memos Docker docs](https://usememos.com/docs/deploy/docker)
  
- [Neon and Vercel manual setup](https://neon.com/docs/guides/vercel-manual)