Deploying

Deploy a Replit app to a VPS

How to move a Replit app to a VPS you control: push it to GitHub, turn the .replit build and run commands into a Dockerfile, re-create the secrets, and move the production database with pg_dump.

TL;DR

  • Replit (opens in a new tab) publishes apps as Autoscale, Static, Reserved VM or Scheduled deployments on its own Google Cloud (opens in a new tab) infrastructure, and on a VPS each becomes a container, static files or a cron job.
  • Push the code to GitHub (opens in a new tab) from the Git pane; secrets and App Storage files do not travel with it.
  • Your Dockerfile runs the build and run commands from the [deployment] section of .replit, with the app listening on 0.0.0.0, not localhost, as a published Replit app already does.
  • Copy the production database with pg_dump and the production connection string from the Database tool, then pg_restore it into PostgreSQL (opens in a new tab) on the server.
On this page

To deploy a Replit app on your own VPS, push its code to GitHub from the Git pane, turn the build and run commands in its .replit file into a Dockerfile, enter its secrets again on the server, and copy its production PostgreSQL database across with pg_dump and pg_restore. Run the app, the database and a reverse proxy with automatic HTTPS together with Docker (opens in a new tab) Compose, test on a temporary address, then move the domain. This is one of the builder guides in deploying an AI-built app to your own server.

The Replit facts here come from Replit's documentation, checked on 11 November 2026. The Dockerfile, the Compose file and the database copy were run that day in Docker, on Node.js (opens in a new tab) 22 and PostgreSQL 17, with a small Express and PostgreSQL app standing in for a Replit app and a second PostgreSQL container standing in for Replit's production database.

How Replit deploys apps, and what that becomes on a VPS

Replit runs published apps on Google Cloud in the United States, as one of four deployment types, each with a plain equivalent on your own server:

Replit's deployment types and their equivalent on a VPS
Replit deploymentWhat Replit says it is forOn your VPS
AutoscaleApps with variable traffic; resources follow usageA container that is always running
Reserved VMApps that must run continuously, such as a botA container that is always running
StaticSites that do not change with user inputStatic files served by the web server
ScheduledJobs that run at times you chooseA cron entry that runs the job in a container

A VPS does not scale up and down on its own the way Autoscale does. For most small apps that is fine: one server sized for the normal load costs the same every month. If the app has sharp peaks, keep them in mind when you choose the size.

Step 1: Read the deployment settings

The .replit file. Its [deployment] section has a build command that runs before the deployment and a run command that starts it, for example:

[deployment]
build = "npm run build"
run = "npm start"

Its [[ports]] entries map the port the app listens on (localPort) to the public one (externalPort); Replit binds the first port to external port 80. Autoscale and Reserved VM deployments expose only one external port, and a published app must not listen on localhost, so if your app already works published on Replit, it listens on 0.0.0.0 and will behave the same in a container. Note its localPort: the Dockerfile and the Caddyfile below use 3000, so change both if your app listens on another port.

The Publishing tool. Note the deployment type, the custom domain if you have one, and under Adjust settings the names of the Production app secrets, which you will need on the server.

Step 2: Push the code to GitHub

In the workspace, open Tools, then Git under Setup. If there is no repository yet, choose Initialize Git Repository. Connect it to GitHub, stage and commit your files, and push to a private repository you created there. From the Shell, git remote add origin <url> and git push origin main work too; use a personal access token, not your password.

Replit's guide to copying a project says what does and does not travel:

  • Code: GitHub receives only what you commit and push. Ignored files and uncommitted changes stay behind.
  • Secrets: secret values never leave the project. You enter them again by hand, deployment secrets included.
  • App Storage: buckets do not move. Replit's guide moves them between projects by downloading the objects and uploading them to a new bucket, and the same route takes them to object storage you choose.
  • Database: not in the repository. Step 5 copies it.

Clone the repository on your computer and run it once: it is the quickest way to find every environment variable the app reads.

Step 3: Write the Dockerfile

The Dockerfile does what Replit did: install the dependencies, run the build command, start the run command. For a Node.js app whose .replit says build = "npm run build" and run = "npm start", with npm start running node server.js:

FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --no-audit --no-fund
COPY . .
RUN npm run build --if-present
RUN npm prune --omit=dev

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production PORT=3000
COPY --from=build --chown=node:node /app ./
USER node
EXPOSE 3000
CMD ["node", "server.js"]

Replace node server.js with whatever your start script runs, and check two things:

  • If the run command needs a development dependency (for example tsx to run TypeScript directly), remove the npm prune line or move that package to dependencies.
  • If the app builds a frontend with Vite, any VITE_ variable it reads is compiled into the build. Pass it as a build argument (ARG VITE_... before npm run build), not only at run time.

A .dockerignore keeps Replit's files and local secrets out of the image:

node_modules
.git
.env*
.replit
replit.nix
*.dump

Step 4: Run the app, PostgreSQL and Caddy together

On the server, one compose.yaml runs the app, a PostgreSQL 17 database and Caddy (opens in a new tab), which gets and renews the HTTPS certificate for your domain:

services:
  app:
    build: .
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
    env_file: .env.production
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: app
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: unless-stopped

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

volumes:
  db_data:
  caddy_data:
  caddy_config:

The Caddyfile next to it:

app.example.com {
	reverse_proxy app:3000
}

Replace app.example.com with your domain. Put a long random DB_PASSWORD (letters and digits only, because it goes into a URL: openssl rand -hex 32 makes one) in a .env file next to compose.yaml (Docker Compose reads it for the ${DB_PASSWORD} substitutions), and the app's secrets from step 1 in .env.production. Neither file goes into Git. The database port is not published, so only the app can reach it. Start the database first, so you can load the data before the app starts writing:

docker compose up -d db

A dump restores into the same or a newer PostgreSQL major version, so check the version of your Replit production database (select version();) and use at least that version here.

Step 5: Copy the production database

Replit keeps two databases. The workspace, the Shell and the Agent use the development one; the published app uses a separate production one, with its own DATABASE_URL, and that is where your live data is. The development database cannot be reached from outside the app at all, but the production one can, with its connection string from the Database tool: select Production, then open Settings.

In the Replit Shell, run the export from Replit's own guide, with the production connection string in place of $DATABASE_URL:

pg_dump -Fc "<production connection string>" --no-owner --no-privileges -f backup.dump

-Fc writes PostgreSQL's compressed custom format, and the two --no- flags leave out ownership and grants that refer to Replit's database users. Download backup.dump, copy it to the server next to compose.yaml (for example with scp), and restore it into the db service:

docker compose exec -T db pg_restore --no-owner --no-privileges -U app -d app < backup.dump

Then start everything:

docker compose up -d --build

In our test, a 42-row table dumped from the stand-in production database with the first command and restored with the second came back complete, and the app read all 42 rows through Caddy over HTTPS (with localhost as the site address, where Caddy uses its own local certificate authority; a public certificate for a real domain needs a real server).

Do not paste the connection string into a chat, a ticket or a screenshot: it contains the password. If it leaks, regenerate the production credentials in Replit.

For the final move, dump again right before you switch the domain, so no writes made in between are lost, and pause writes on the Replit app while you do. The server's database already holds the test copy by then, so restore with --clean --if-exists, which drops the objects in the dump before it loads them, as Replit's own guide does:

docker compose exec -T db pg_restore --clean --if-exists --no-owner --no-privileges -U app -d app < backup.dump

In our test, a row added after the first restore was gone after this one, and the table held the dump's 42 rows again.

Step 6: Check what is Replit-specific

These work on Replit only because Replit is there:

  • REPLIT_DEPLOYMENT. A published Replit app runs with REPLIT_DEPLOYMENT=1, so code that checks it to decide whether it is in production will think it is in development on your server. Search for it and use NODE_ENV or your own variable.
  • Replit Auth. Replit Auth signs users in with their Replit accounts, through Replit's shared OAuth apps set up by the Agent. Replit's docs do not describe using it from another host, so plan to replace it; Replit documents a migration from Replit Auth to Clerk, and any other provider works too.
  • The .replit.app address. Search the code and the secrets for it: OAuth callbacks, webhooks (Stripe, GitHub) and email links can have it hardcoded.
  • The port. If the app hardcodes a port instead of reading PORT, set PORT to that number and change the Caddyfile to match.

Step 7: Test, then switch the domain

Use a temporary subdomain first: point it at the server and put it in the Caddyfile instead of the real domain. Sign in, create and change something, upload a file, and wait for any scheduled job to run. Then point your domain's A record at the server, remove the custom domain from Replit's Publishing settings, and unpublish the Replit app once the new one has run cleanly for a while.

No server yet? How to deploy a web app to a VPS covers creating one, SSH keys, the firewall and Docker; Docker Compose on Hetzner with GitHub Actions shows how to deploy every push automatically; and zero-downtime deployments with Docker Compose keeps updates from dropping visitors. If your Replit app is a Next.js (opens in a new tab) app, the v0 guide has a standalone Next.js Dockerfile, and if it is a static Vite site, the Lovable guide has one for that.

Doing this with OpsCrew

OpsCrew recognises a repository exported from Replit and runs the same steps from it: a free opscrew scan for leaked keys first, then an image built in GitHub Actions (with its own Dockerfile for Node.js and Python apps, or yours), a hardened server on Hetzner Cloud (opens in a new tab) or DigitalOcean (opens in a new tab) or one you already have, and HTTPS on your domain. When the app uses PostgreSQL, it runs the database next to it with a generated password and gives the app its DATABASE_URL, or keeps a hosted database if you prefer. Copying your existing data in is still the pg_dump and pg_restore above. Every change waits for your approval of its exact plan.

For what is the same across every builder, see the guide to deploying an AI-built app to your own server.

Key facts

FactValueSource
Replit deployment typesAutoscale, Static, Reserved VM and ScheduledPublishing (opens in a new tab)Verified on
Where Replit hosts published appsGoogle Cloud in the United States; Enterprise customers can ask for EU hostingPublishing (opens in a new tab)Verified on
Variable a published Replit app runs withREPLIT_DEPLOYMENT=1Development and production databases (opens in a new tab)Verified on
External access to the development databaseNone; only the production database's connection string works from outside the appConnection details (opens in a new tab)Verified on

Frequently asked questions

How does Replit deploy apps?

Publishing saves a snapshot of the app's files and dependencies and runs it on Replit's cloud, separate from the editor, and you publish again to update it.

Can I run a Replit app locally?

Yes. Clone the repository, install the dependencies, set the environment variables the app reads (including a DATABASE_URL for a local PostgreSQL), and run the run command from .replit. For data, use a local database or a dump of the production one.

Sources

  1. Publishing (opens in a new tab), Replit, accessed on
  2. Replit App configuration (opens in a new tab), Replit, accessed on
  3. Ports (opens in a new tab), Replit, accessed on
  4. Using the Git pane (opens in a new tab), Replit, accessed on
  5. Manually copy a project to another workspace (opens in a new tab), Replit, accessed on
  6. Development and production databases (opens in a new tab), Replit, accessed on
  7. Connection details (opens in a new tab), Replit, accessed on
  8. Storage and databases (opens in a new tab), Replit, accessed on
  9. Auth (opens in a new tab), Replit, accessed on
  10. pg_dump (opens in a new tab), PostgreSQL Global Development Group, accessed on
  11. Automatic HTTPS (opens in a new tab), Caddy, accessed on
  • Deploying

    How to deploy an app you built with AI to your own server

    From a Lovable, Bolt.new, Replit, v0 or Claude Code project to a live URL on a server you control: code, build, backend, secrets, HTTPS and what breaks on the way.

    10 min read

  • Deploying

    Deploy a Bolt.new app to your own server

    How to deploy a Bolt.new app outside Bolt Cloud: get the code, find out which stack Bolt chose, build it in Docker, serve it over HTTPS, and claim the database into Supabase.

    8 min read

  • Deploying

    Deploy a Lovable app to your own server (Vite or TanStack Start)

    How to deploy a Lovable app to a server you control: tell the Vite and TanStack Start stacks apart, sync it to GitHub, build it in Docker, serve it over HTTPS, and keep or move the Supabase backend.

    9 min read

Put OpsCrew to work on your app.

Run npx opscrew init in your app's repository. It checks your tools and tokens first, for free.