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)