Frameworks & Docker

PreviewDrop runs anything that speaks HTTP in a container. Here's what works out of the box and how to bring your own image.

The two rules
Your app must (1) listen on the port set in the PORT environment variable and (2) bind to 0.0.0.0, not 127.0.0.1. Get those right and everything else is auto-detection.

Auto-detection

If your repo doesn't have a Dockerfile, PreviewDrop inspects the project, picks a sensible build plan, and runs it. Supported stacks include:

  • Node.js — package.json with start script, pnpm/yarn/npm lockfiles.
  • Python — requirements.txt, Poetry (pyproject.toml), Pipenv.
  • Ruby — Gemfile with Rails or Sinatra.
  • Go — go.mod + main.go.
  • Java / Kotlin — Maven (pom.xml), Gradle (build.gradle).
  • PHP — composer.json with Laravel, Symfony, or plain PHP.
  • Rust — Cargo.toml.
  • .NET — .csproj.

Auto-detection almost always gets the basics right. If you need more control (custom build steps, a specific package version, extra system packages), add a Dockerfile and PreviewDrop will use it instead.

Bring your own Dockerfile

This is the recommended path for real apps. It's the same Dockerfile you'd run in production — PreviewDrop doesn't need a special one. Two things to check:

Listen on $PORT and 0.0.0.0

PreviewDrop sets PORT for your container. Make your app honour it.

Dockerfile
# Node ENV PORT=3000 CMD ["node", "server.js"] # server.js: app.listen(process.env.PORT, "0.0.0.0") # Python (uvicorn) CMD ["sh", "-c", "uvicorn app:app --host 0.0.0.0 --port $PORT"] # Rails CMD ["sh", "-c", "bundle exec rails s -b 0.0.0.0 -p $PORT"]

Expose the port

EXPOSE 3000 (or whichever port) lets PreviewDrop know where to route traffic. If you omit it, we default to 3000; you can override in Project Settings → Exposed port.

Framework examples

Django

Quick template below. For the hardened Dockerfile with system deps, gunicorn workers, env vars, common gotchas (ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, Celery workers), and branch-isolated databases, see PreviewDrop for Django.

Dockerfile
FROM python:3.12-slim WORKDIR /app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["sh", "-c", "python manage.py migrate --noinput && gunicorn project.wsgi --bind 0.0.0.0:$PORT"]

Set ALLOWED_HOSTS=* in your env vars for previews — or .previews.previewdrop.dev if you want it tighter.

Rails

Quick template. For the hardened Dockerfile (libvips, precompile with dummy SECRET_KEY_BASE, full env var list) plus gotchas (force_ssl proxies, Sidekiq workers, ActionMailer in previews), see PreviewDrop for Rails.

Dockerfile
FROM ruby:3.3-slim RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs WORKDIR /app COPY Gemfile* ./ RUN bundle install --jobs 4 COPY . . RUN bundle exec rake assets:precompile EXPOSE 3000 CMD ["sh", "-c", "bundle exec rails db:migrate && bundle exec rails s -b 0.0.0.0 -p $PORT"]

Laravel

Quick template. For the hardened Dockerfile (Composer 2 builder stage, Apache rewrite, full env list including APP_KEY + APP_URL wiring), trusted-proxies config, and queue worker pattern, see PreviewDrop for Laravel.

Dockerfile
FROM php:8.3-apache RUN docker-php-ext-install pdo pdo_mysql COPY . /var/www/html WORKDIR /var/www/html RUN composer install --no-dev --optimize-autoloader ENV APACHE_DOCUMENT_ROOT=/var/www/html/public RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf EXPOSE 80

Apache doesn't read $PORT natively — set the exposed port in Project Settings to 80.

FastAPI

Dockerfile
FROM python:3.12-slim WORKDIR /app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port $PORT"]

Spring Boot

Dockerfile
FROM eclipse-temurin:21-jre WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 CMD ["sh", "-c", "java -jar app.jar --server.port=$PORT --server.address=0.0.0.0"]

Next.js / Node

Dockerfile
FROM node:20-slim AS deps WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:20-slim WORKDIR /app COPY --from=deps /app/.next ./.next COPY --from=deps /app/public ./public COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/package.json ./ EXPOSE 3000 CMD ["npm", "start"]

Build cache

PreviewDrop caches build layers across deployments of the same project, so repeat builds finish much faster than the first one. Put your dependency install step (npm ci, bundle install, pip install -r requirements.txt) before the COPY . .so dependency layers don't invalidate on every commit.

What if my app needs a database?

Two patterns work well:

  • Shared dev DB. Point every preview at the same shared dev/staging Postgres via DATABASE_URL. Simplest; good for early-stage teams.
  • Branch DBs. Pair PreviewDrop with Neon or PlanetScale branching. Use their per-branch DB URL as the preview's DATABASE_URL and migrations stay isolated.

See Environment variables for how to wire branch-specific values.