Skip to main content

Dockerfile: Create Unprivileged Webserver Containers with Nginx(Alpine), Apache(Alpine / from scratch) & Caddy(Alpine)

821 words·
Dockerfile Unprivileged Container Alpine Webserver Nginx Apache Caddy
Table of Contents

Example Website
#

File and Folderstructure
#

static-site
├── index.html
├── css
│   └── styles.css
└── js
    └── scripts.js

HTML File
#

  • static-site/index.html
<!DOCTYPE html>
<html>

<head>
    <title>jklug.work</title>
    <link rel="stylesheet" type="text/css" href="css/styles.css">
</head>

<body>
    <h1>Some HTML</h1>
    <p>Hi there, click me.</p>
    <!-- Add a button -->
    <button id="myButton">Click Me</button>

    <!-- Link to the JavaScript file -->
    <script src="js/scripts.js"></script>
</body>

</html>

CSS File
#

  • static-site/css/styles.css
/* General styling for the body */
body {
    font-family: Arial, sans-serif;
    background-color: #f0f0f0;
    margin: 0;
    padding: 0;

    /* Flexbox centering */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100vh; /* Full viewport height */
}

/* Header styling */
h1 {
    color: #333;
    margin: 0 0 10px 0; /* Remove default margin and add spacing */
}

/* Paragraph styling */
p {
    color: #666;
    font-size: 18px;
    margin: 10px 0; /* Add some spacing */
}

/* Button styling */
button {
    background-color: #d2691e;
    color: white;
    border: none;
    padding: 10px 20px;
    font-size: 16px;
    border-radius: 5px;
    cursor: pointer;
    margin-top: 20px; /* Add space above the button */
}

/* Button hover effect */
button:hover {
    background-color: #8b4513;
}

JavaScript File
#

  • static-site/js/scripts.js
// Function to handle button clicks
function handleButtonClick() {
    const paragraph = document.querySelector("p");
    // Toggle text between text
    if (paragraph.textContent === "Hi there, click me.") {
        paragraph.textContent = "Button was clicked.";
    } else {
        paragraph.textContent = "Hi there, click me.";
    }
}

// Attach event listener when the DOM is ready
document.addEventListener("DOMContentLoaded", () => {
    const button = document.querySelector("#myButton");
    button.addEventListener("click", handleButtonClick);
});



Unprivileged Webserver Containers
#

Nginx: Alpine based Unprivileged
#

Dockerfile
#

  • Dockerfile
# Alpine-based NGINX image with unprivileged (non-root) default user
FROM nginxinc/nginx-unprivileged:stable-alpine

# Temporarily switch to "root" user to perform permission adjustments
USER root

# Set variable for the document root
ARG DOCROOT=/usr/share/nginx/html

# Copy website files into the document root and set ownership to the "nobody" user
COPY --chown=nobody:nobody static-site/ ${DOCROOT}

# Adjust permissions
RUN find ${DOCROOT} -type d -print0 | xargs -0 chmod 755 && \
    find ${DOCROOT} -type f -print0 | xargs -0 chmod 644 && \
    chmod 755 ${DOCROOT}

# Switch back to the "nginx" user for running the container
USER nginx

# Expose port 8080 for the container
EXPOSE 8080

Build & Verify the Container
#

# Build the Docker image
docker build -t nginx-alpine-unprivileged .
# Verify container image
docker images

# Shell output:
REPOSITORY                  TAG       IMAGE ID       CREATED          SIZE
nginx-alpine-unprivileged   latest    29b8db339527   14 minutes ago   48.8MB
# Run the Docker container
docker run -d -p 8080:8080 --name static-site nginx-alpine-unprivileged
# Verify Nginx
curl localhost:8080

# Shell output:
<!DOCTYPE html>
<html>

<head>
    <title>jklug.work</title>
    <link rel="stylesheet" type="text/css" href="css/styles.css">
</head>

Verify user:

# Access container terminal
docker exec -it static-site sh

# List user details
id

# Shell output:
uid=101(nginx) gid=101(nginx) groups=101(nginx)



Apache: From scratch / Alpine
#

Dockerfile
#

  • Dockerfile
# Use the Alpine base image
FROM alpine:latest

# Install Apache2
RUN apk update && apk add apache2 && rm -rf /var/cache/apk/*

# Copy website files to the document root
COPY static-site/ /var/www/localhost/htdocs/

# Set ownership and permissions for Apache directories
RUN chown -R apache:apache /var/www && \
    chown -R apache:apache /run/apache2 && \
    chown -R apache:apache /var/log/apache2 && \
    chmod -R 770 /var/run/apache2 && \
    chmod -R 770 /var/log/apache2 && \
    chown -R apache:apache /etc/apache2

# Start Apache2 using non-root user
USER apache

# Expose the default Apache port
EXPOSE 80

# Start Apache
ENTRYPOINT ["/usr/sbin/httpd", "-D", "FOREGROUND"]

Build & Verify the Container
#

# Build the Docker image
docker build -t apache-fromscratch-alpine .
# Verify container image
docker images

# Shell output:
REPOSITORY                  TAG       IMAGE ID       CREATED              SIZE
apache-fromscratch-alpine   latest    f5a93e58f0f5   About a minute ago   11.8MB
# Run the Docker container
docker run -d -p 8080:80 --name static-site apache-fromscratch-alpine
# Verify Apache
curl localhost:8080

# Shell output:
<!DOCTYPE html>
<html>

<head>
    <title>jklug.work</title>
    <link rel="stylesheet" type="text/css" href="css/styles.css">
</head>

Verify user:

# Access container terminal
docker exec -it static-site sh

# List user details
id

# Shell output:
uid=100(apache) gid=101(apache) groups=82(www-data),101(apache)



Caddy: Alpine
#

Dockerfile
#

  • Dockerfile
# Use the official Caddy image as the base
FROM caddy:alpine

# Create a non-root user "caddy"
RUN addgroup -S caddy && adduser -S -G caddy caddy

# Adjust permissions
RUN mkdir -p /usr/share/caddy && \
    chown -R caddy:caddy /usr/share/caddy /config /data

# Copy website files into the container
COPY static-site/ /usr/share/caddy

# Switch to the non-root user
USER caddy

# Expose the default Caddy port
EXPOSE 80

Build & Verify the Container
#

# Build the Docker image
docker build -t caddy .
# Verify container image
docker images

# Shell output:
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
caddy        latest    f88632afc0c4   4 seconds ago   49.3MB
# Run the Docker container
docker run -d -p 8080:80 --name static-site caddy
# Verify Caddy
curl localhost:8080

# Shell output:
<!DOCTYPE html>
<html>

<head>
    <title>jklug.work</title>
    <link rel="stylesheet" type="text/css" href="css/styles.css">
</head>

Verify user:

# Access container terminal
docker exec -it static-site sh

# List user details
id

# Shell output:
uid=100(caddy) gid=101(caddy) groups=101(caddy)