Lovable generates standard Vite+React+TypeScript projects with no proprietary dependencies, making them fully containerizable with Docker. Export your project via GitHub, write a multi-stage Dockerfile using node:22 for the build and nginx for static serving, and deploy the container to AWS ECS, Google Cloud Run, or any Docker-compatible host. Docker gives you reproducible builds and infrastructure-agnostic deployments.
Containerizing a Lovable Project with Docker for Flexible Deployment
Lovable deploys projects to its own Lovable Cloud hosting by default, which is the easiest option for most use cases. But there are legitimate reasons to containerize and self-host: compliance requirements that mandate data residency in specific regions, cost optimization at scale, integration with existing Kubernetes clusters, or enterprise policies that prohibit third-party hosting. Because Lovable generates a completely standard Vite+React+TypeScript project with no proprietary runtime dependencies, containerizing it with Docker is straightforward.
The build process has two stages. First, a node:22 container installs dependencies and runs npm run build, producing a static dist/ folder containing your compiled HTML, JavaScript, and CSS. Second, an nginx container serves that static output. The final image is remarkably small — typically under 50 MB — because it only contains nginx and the compiled static files, not Node.js itself. This two-stage approach is a Docker best practice called a multi-stage build, and it is the standard pattern for React app containerization.
For local development, docker-compose adds value by mounting your source code as a volume and running the Vite dev server inside a container, giving you hot module replacement without installing Node.js locally. For production, the same Dockerfile works on AWS ECS, Google Cloud Run, DigitalOcean App Platform, or any server with Docker installed. The key consideration is environment variables: Supabase credentials are build-time variables in Vite (prefixed with VITE_), so they need to be passed to docker build as build arguments, not as runtime environment variables injected into the running container.
Integration method
Lovable exports projects as standard Vite+React+TypeScript code via GitHub. You containerize this output with a Dockerfile: a node:22 build stage runs npm run build to produce a dist/ folder, and an nginx stage serves it as a static site. The resulting container image can be deployed anywhere Docker runs — local machines, AWS ECS, Google Cloud Run, DigitalOcean, or self-hosted servers.
Prerequisites
- A Lovable project connected to a GitHub repository (see the VS Code integration guide for GitHub setup steps)
- Docker Desktop installed on your computer — download from docker.com/products/docker-desktop
- A GitHub account to clone your Lovable project repository
- Basic familiarity with opening a terminal and running commands
- For cloud deployment: an account with AWS, Google Cloud, or DigitalOcean (free tiers available on all three)
Step-by-step guide
Connect Lovable to GitHub and clone the project
Connect Lovable to GitHub and clone the project
Docker works with the code files that Lovable generates, so you first need to export your project from Lovable to GitHub. Open your Lovable project in the browser. Click the GitHub icon in the top-right corner. If your project is not already connected, follow the OAuth flow to authorize Lovable with your GitHub account, then click 'Connect project' and choose to create a new repository or link an existing one. Once connected, the GitHub icon turns green, indicating real-time sync is active. Now open your terminal and clone the repository using the HTTPS URL from GitHub: navigate to the repository page, click the green 'Code' button, copy the URL, and run git clone followed by the URL. Once cloned, open the folder in your terminal. You will see the standard Lovable project structure: src/ for React components, supabase/ for Edge Functions, vite.config.ts, tailwind.config.ts, tsconfig.json, and package.json at the root. Confirm that the project builds cleanly before dockerizing: run npm install followed by npm run build and verify that a dist/ folder appears containing index.html and the compiled JavaScript and CSS assets. If the build succeeds locally, it will succeed inside Docker. If it fails, fix the build errors first — they will cause the Docker build to fail at the same step.
Pro tip: Run npm run build locally before writing your Dockerfile. A successful local build confirms the project structure is correct and helps you spot environment variable issues before they become Docker problems.
Expected result: You have a local clone of your Lovable project. Running npm run build produces a dist/ folder without errors. You are ready to write the Dockerfile.
Write a multi-stage Dockerfile for the Vite+React project
Write a multi-stage Dockerfile for the Vite+React project
Create a file named Dockerfile in the root of your project — the same directory as package.json. The Dockerfile uses a two-stage build pattern: the first stage installs dependencies and compiles the TypeScript/React code into static files, and the second stage copies only those static files into a minimal nginx image. This keeps the final Docker image small by excluding Node.js and all the node_modules from the production image. The build stage uses node:22-alpine (the Alpine Linux variant is much smaller than the default Debian image). It sets the working directory, copies package.json and package-lock.json first (so Docker caches the npm install layer and only re-runs it when dependencies change), installs dependencies with npm ci (cleaner and faster than npm install for CI/CD), then copies the rest of the source code and runs npm run build. The VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are declared as ARG build arguments — they must be passed at build time because Vite embeds these values into the compiled JavaScript bundle at build time, not at runtime. The nginx stage starts from nginx:alpine, copies the dist/ folder output from the build stage into nginx's web root, and copies a custom nginx.conf that handles single-page application routing (redirecting all paths back to index.html so React Router works correctly). The final image serves the app on port 80.
1# Stage 1: Build2FROM node:22-alpine AS builder34WORKDIR /app56# Copy package files first for better layer caching7COPY package.json package-lock.json ./8RUN npm ci910# Build args for Vite environment variables11# These are embedded into the bundle at build time12ARG VITE_SUPABASE_URL13ARG VITE_SUPABASE_ANON_KEY14ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL15ENV VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY1617# Copy source and build18COPY . .19RUN npm run build2021# Stage 2: Serve with nginx22FROM nginx:alpine2324# Copy built files25COPY --from=builder /app/dist /usr/share/nginx/html2627# Copy nginx config for SPA routing28COPY nginx.conf /etc/nginx/conf.d/default.conf2930EXPOSE 803132CMD ["nginx", "-g", "daemon off;"]Pro tip: The ARG declarations must come after the FROM line for the stage where they are used. Build args declared before the first FROM are only available to FROM instructions themselves (for dynamic base image selection).
Expected result: A Dockerfile exists in your project root. The file has two FROM stages: node:22-alpine for building and nginx:alpine for serving.
Create the nginx configuration for SPA routing
Create the nginx configuration for SPA routing
React Router uses the HTML5 History API to handle navigation — URLs like /dashboard or /settings are handled client-side by React, not by the server. Without a special nginx configuration, requesting /dashboard directly (or refreshing the page on that route) would return a 404 error because there is no actual file at that path — only the root index.html exists. You need to configure nginx to serve index.html for any path that does not match an existing file. Create a file called nginx.conf in your project root. The configuration defines a server block listening on port 80, sets the root directory to /usr/share/nginx/html where the Vite build output lives, and uses the try_files directive to attempt serving the exact file path first, then fall back to index.html if no file is found. This is the standard SPA routing configuration and is required for any React Router application served statically. The configuration also sets the index file to index.html and enables gzip compression for better performance. The location block for static assets (JavaScript, CSS, images) sets aggressive cache headers since Vite generates content-hashed filenames — the same file content always has the same hash in the filename, so it is safe to cache indefinitely. The main location block sets no-cache headers for index.html itself, ensuring users always get the latest version of the app.
1server {2 listen 80;3 server_name _;4 root /usr/share/nginx/html;5 index index.html;67 # Enable gzip compression8 gzip on;9 gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;1011 # Cache static assets with content hashes forever12 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {13 expires 1y;14 add_header Cache-Control "public, immutable";15 }1617 # Never cache index.html18 location = /index.html {19 add_header Cache-Control "no-cache, no-store, must-revalidate";20 }2122 # SPA routing: serve index.html for all other paths23 location / {24 try_files $uri $uri/ /index.html;25 }26}Pro tip: If your Lovable app is deployed at a subdirectory path (e.g., example.com/app/), you need to set the base path in vite.config.ts using the base option and adjust the nginx location block accordingly.
Expected result: An nginx.conf file exists in your project root. React Router navigation will work correctly when the container is running — direct URL access to /dashboard and other routes will load the React app, not return 404.
Build and test the Docker image locally
Build and test the Docker image locally
With the Dockerfile and nginx.conf in place, you can now build and test the Docker image on your local machine before deploying to the cloud. Open your terminal in the project root. Build the Docker image by running the docker build command. You need to pass your Supabase credentials as build arguments using the --build-arg flag because Vite embeds them into the compiled JavaScript at build time. Replace the placeholder values with your actual Supabase project URL and anon key — you can find these in Lovable's Cloud tab by clicking the + icon next to Preview, or in your Supabase project settings under Project Settings → API. Give the image a name using the -t flag. The build process runs both stages: the builder stage installs dependencies (this takes one to three minutes the first time) and compiles the TypeScript code, then the nginx stage creates a minimal image with only the compiled output. Once the build succeeds, run the container locally using docker run with port mapping to expose port 80 inside the container as port 8080 on your machine. Open http://localhost:8080 in your browser to verify the app loads and navigation works. Test a direct URL like http://localhost:8080/some-page to confirm the nginx SPA routing is working correctly — you should see the React app, not a 404 page.
1# Build the image (replace with your actual Supabase values)2docker build \3 --build-arg VITE_SUPABASE_URL=https://your-project-id.supabase.co \4 --build-arg VITE_SUPABASE_ANON_KEY=your-anon-key-here \5 -t my-lovable-app:latest .67# Run the container locally8docker run -p 8080:80 my-lovable-app:latest910# Or run in background11docker run -d -p 8080:80 --name lovable-app my-lovable-app:latestPro tip: Add a .dockerignore file to your project root listing node_modules, .git, .env.local, and dist. This prevents Docker from copying these folders into the build context, making builds faster.
Expected result: The docker build command completes without errors and produces an image. Running the container and opening http://localhost:8080 shows your Lovable app. All routes work correctly. The Docker image size should be between 20 MB and 60 MB.
Set up docker-compose for local development with hot reload
Set up docker-compose for local development with hot reload
The production Dockerfile builds a static image, but for local development you want hot module replacement — changes to React components should appear in the browser immediately without rebuilding the entire image. Docker Compose solves this by mounting your source code as a volume into a Node.js container running the Vite dev server. Create a docker-compose.yml file in your project root. The configuration defines a service using the node:22-alpine image, mounts the entire project directory as a volume, overrides the default command to run npm run dev, sets the VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY environment variables using your .env.local file, and maps port 5173 from the container to port 5173 on your host machine. Vite's dev server is configured by default to listen on port 5173. After creating this file, create the .env.local file if it does not already exist and add your Supabase credentials. Run docker compose up to start the development server. Docker will pull the node:22-alpine image, install dependencies inside the container, and start the Vite dev server. Open http://localhost:5173 in your browser to see the app running. Changes you make to files in the src/ directory will trigger instant hot module replacement in the browser, just as if you had run npm run dev directly on your machine — except everything runs inside Docker.
1version: '3.8'23services:4 app:5 image: node:22-alpine6 working_dir: /app7 volumes:8 - .:/app9 - /app/node_modules10 command: sh -c "npm install && npm run dev -- --host"11 ports:12 - "5173:5173"13 env_file:14 - .env.local15 environment:16 - NODE_ENV=developmentPro tip: The -- --host flag passes --host to the Vite dev server, making it listen on all network interfaces inside the container (0.0.0.0) rather than only localhost. This is required for port mapping to work correctly from container to host.
Expected result: Running docker compose up starts the Vite dev server inside a container. Opening http://localhost:5173 shows the app. Editing a React component in VS Code triggers an instant update in the browser through hot module replacement.
Common use cases
Deploy a Lovable app to a self-hosted VPS or internal server
Teams with on-premise infrastructure or compliance requirements can containerize their Lovable project and run it on any Linux server with Docker installed. Build the image locally, push it to a container registry, and pull and run it on the server — no Lovable Cloud subscription required for hosting.
I want to self-host my Lovable app using Docker. Can you generate a production-ready Dockerfile for a Vite React TypeScript project that uses nginx to serve static files?
Copy this prompt to try it in Lovable
Run the Lovable project locally without installing Node.js
Developers who do not want to install Node.js globally can use docker-compose to run the Vite development server inside a container. Source code is mounted as a volume so changes on the host machine trigger hot reload inside the container.
Create a docker-compose.yml file for local development of my Lovable Vite React project with hot reload enabled.
Copy this prompt to try it in Lovable
Deploy to Google Cloud Run for serverless container hosting
Google Cloud Run scales containers from zero to many instances based on traffic and charges only for actual request processing time, making it cost-effective for apps with variable traffic. Build the Lovable project image, push to Google Artifact Registry, and deploy to Cloud Run with a single command.
Help me write a Cloud Build configuration to automatically build and deploy my Lovable project to Google Cloud Run when I push to the main GitHub branch.
Copy this prompt to try it in Lovable
Troubleshooting
Docker build fails with 'VITE_SUPABASE_URL is not defined' or blank pages after deployment
Cause: Vite environment variables are embedded at build time. If the --build-arg values were not passed during docker build, or they were passed as runtime environment variables (docker run -e), they will be undefined in the compiled bundle.
Solution: Always pass Supabase credentials as --build-arg during docker build, not as -e during docker run. Runtime environment variables injected with docker run are not visible to the Vite build process. Rebuild the image with the correct --build-arg flags.
1docker build --build-arg VITE_SUPABASE_URL=https://xxx.supabase.co --build-arg VITE_SUPABASE_ANON_KEY=your-key -t my-app .Navigating directly to a route like /dashboard returns a 404 page
Cause: The nginx.conf file is missing or does not include the try_files $uri $uri/ /index.html directive needed for SPA routing.
Solution: Verify that nginx.conf exists in your project root, is copied into the Dockerfile with COPY nginx.conf /etc/nginx/conf.d/default.conf, and contains the try_files fallback. Rebuild the Docker image after updating the nginx config.
docker compose up fails with 'port is already allocated' error
Cause: Another process (likely npm run dev running directly) is already using port 5173 or 8080 on your host machine.
Solution: Stop the conflicting process — find it with lsof -i :5173 on macOS/Linux. Alternatively, change the host port in docker-compose.yml from 5173:5173 to a different port like 3000:5173.
Changes to source files are not reflected in the browser when using docker-compose
Cause: The node_modules volume override is not working correctly, or the --host flag was not passed to the Vite dev server.
Solution: Make sure the docker-compose.yml command includes -- --host after npm run dev. Also confirm the volumes section has both the project mount (.: /app) and the node_modules override (/app/node_modules) — the latter prevents the container's node_modules from being overwritten by the host's (potentially empty or incompatible) node_modules folder.
Best practices
- Always use multi-stage Docker builds for Vite/React projects — the final nginx image should never contain Node.js or node_modules, keeping image sizes under 60 MB.
- Pass Supabase credentials as build arguments during docker build, not as runtime environment variables — Vite embeds VITE_-prefixed variables into the JavaScript bundle at build time.
- Add a .dockerignore file listing node_modules, .git, dist, and .env.local to prevent unnecessary files from being sent to the Docker build context, which speeds up builds significantly.
- Use nginx for serving the static Vite output in production — never use node serve or http-server in production Docker images as they lack performance and security hardening.
- Tag production images with semantic version numbers (my-app:1.2.3) rather than only 'latest' — this makes rollback straightforward and prevents confusion about which version is running.
- For teams, push Docker images to a private container registry (AWS ECR, Google Artifact Registry, or GitHub Container Registry) rather than rebuilding on the deployment server.
- Test the production Docker image locally with docker run before pushing to a cloud registry — catching nginx config or environment variable issues locally is far faster than debugging in a cloud environment.
Alternatives
Choose VS Code when you want to edit Lovable's source code locally with IDE tooling — Docker is for packaging and deploying the app, not for development editing.
Choose IntelliJ IDEA if your team works in JetBrains IDEs and wants to edit the Lovable frontend alongside a Java or Kotlin backend in one IDE.
Choose GitLab when you want to combine Docker-based deployment with GitLab CI/CD pipelines — GitLab's built-in registry and runners can automate building and deploying your Lovable Docker image on every push.
Frequently asked questions
Does Lovable support Docker natively?
Lovable does not have a built-in Docker integration. However, because Lovable generates standard Vite+React+TypeScript projects with no proprietary dependencies, you can containerize the project yourself using the GitHub export. The workflow is: connect Lovable to GitHub, clone the repo, write a Dockerfile, and deploy the container anywhere you like.
Why do my Supabase credentials need to be build arguments rather than runtime environment variables?
Vite processes all environment variables prefixed with VITE_ at build time and embeds them directly into the compiled JavaScript bundle. By the time the container is running, the bundle is already compiled and static — there is no Node.js process to read runtime environment variables. Build arguments (--build-arg) are available during the npm run build step when Vite can embed them.
How large will the Docker image be?
A properly configured multi-stage Dockerfile produces a final nginx image typically between 20 MB and 60 MB, depending on the size of your compiled assets. The builder stage is much larger (400-600 MB with node_modules) but is discarded and not part of the final image.
Can I deploy the Lovable Docker container to Kubernetes?
Yes. The nginx static-serving container runs well in Kubernetes. You would create a Deployment manifest pointing to your container registry image, a Service to expose it, and an Ingress for routing. For environment variables, use Kubernetes Secrets or ConfigMaps to inject the Supabase build args during your CI/CD pipeline build step before pushing to the registry.
What is the difference between deploying with Docker versus using Lovable Cloud?
Lovable Cloud is the simplest option — one click, no configuration, managed globally. Docker gives you full control over the hosting environment: choose your cloud provider, region, server size, and network configuration. Docker is the right choice when you have compliance requirements, need specific data residency, or want to integrate with existing infrastructure that already uses containers.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation