Description
I have found what I believe to be a good solution for deploying websites via Docker on a server, for example.
This approach uses a Dockerfile that provides a base Node image for all services requiring Node.
Website-specific Dockerfiles are built on top of this base, avoiding repeated downloads of the Node image for each website.
TL;DR
When your deploying more than one website on a server, you create a base dockerfile and a dockerfile for each website.
The website dockerfiles inherit from the base dockerfile.
The shared base dockerfile
FROM node:22-alpine
# Create consistent user/group for all websites
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjsTo build this container, run:
docker build -f Dockerfile.website-base -t website-base .You only need to build and not run this container, it’s only used as a base for the website-specific dockerfiles.
More details on how to run this container can be found in the Dockerfile Overview.
The website-specific dockerfile
You can copy this template into the root directory of your website or every website you want to deploy.
The only thing you need to change is the Dockerfile.your-website-name to match your website name.
# Your website-base image you created earlier
FROM website-base AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# Build the application
RUN npm run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# Copy production dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy the public folder
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Copy the build output
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]Deploying your website
Now to build your dockerfile for your specific website, copy and adjust this command:
Don’t forget to change website-name to match your website name.
docker build -f Dockerfile.website-name -t website-name .Finally run your container and bring your website to life:
docker run -d --restart=always --name website-name -p 3000:3000 website-nameInclude .env variables
If you need to inject environment variables into your website, add the --env-file flag to your docker run command
docker run --env-file <your-env-filename> -d --restart=always --name website-name -p 3000:3000 website-nameKeep in mind that if you want to deploy multiple websites on the same server, you need to change the port mapping for each website.
For example, the second website could use -p 3001:3000
Automated redeploy script
To simplify the process of rebuilding and redeploying your applications,
you can use the following Bash script. It is recommended to save this script as deploy.sh
in your main webserver directory (one level up from your specific project folders).
This allows you to reference it from any project sub-folder while utilizing tab-completion for your Dockerfile paths.
Script setup
Copy the following script into a file named deploy.sh in your main webserver directory.
#!/bin/bash
# Usage: ./deploy.sh <path-to-dockerfile> <container-name> <port> [env-file]
DOCKERFILE_PATH="$1"
NAME="$2"
PORT="$3"
ENV_FILE="$4"
if [ -z "$DOCKERFILE_PATH" ] || [ -z "$NAME" ] || [ -z "$PORT" ]; then
echo "Usage: $0 <path-to-dockerfile> <container-name> <port> [env-file]"
exit 1
fi
# 1. Build the image first (Safety check)
echo "Building image from $DOCKERFILE_PATH..."
if ! docker build -f "$DOCKERFILE_PATH" -t "$NAME" .; then
echo "Build failed. Aborting deployment to keep current site online."
exit 1
fi
# 2. Stop and Remove old container only after successful build
if [ "$(docker ps -aq -f name=^/${NAME}$)" ]; then
echo "Stopping and removing old container '$NAME'..."
docker stop "$NAME" > /dev/null
docker rm "$NAME" > /dev/null
fi
# 3. Run new container
echo "Starting new container on port $PORT..."
if [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then
docker run --env-file "$ENV_FILE" -d --restart=always --name "$NAME" -p "$PORT":3000 "$NAME"
else
docker run -d --restart=always --name "$NAME" -p "$PORT":3000 "$NAME"
fiSetup script permissions
Make the script executable
chmod +x deploy.shLogic & functionality - IMPORTANT
The script orchestrates a safe-update lifecycle with the following logic:
-
Pre-flight Build: It runs docker build before interacting with the live container. If the build fails (e.g., syntax errors), the script aborts immediately. This guarantees zero downtime caused by broken updates, as the old container remains active.
-
Container Replacement: Once the build succeeds, the script identifies the running container by the provided
<container-name>, stops it, and removes it. -
Naming Constraint: The
<container-name>argument must exactly match the name of the currently running instance. The script relies on this identifier to find the correct process to replace.
Script usage
Usage: ../deploy.sh <path-to-dockerfile> <container-name> <port> [env-file]
../deploy.sh Dockerfile.website-name website-name 127.0.0.1:3000OR
with .env file:
../deploy.sh Dockerfile.website-name website-name 127.0.0.1:3000 .envConclusion
When firewall rules are set up correctly, you should be able to access your website via http://your-server-ip:3000
This approach allows you to deploy multiple Next.js websites on a single server efficiently,
leveraging a shared base image to minimize redundancy and optimize resource usage.
Created: 20.10.2025
Last Updated: 31.12.2025