Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ Homestead.yaml
Homestead.json
node_modules/

# Docker Volumes
/docker/volumes/

# Environment files (contain credentials)
# Only .example files are committed
/docker/environments/dev.env
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
# Dockerized Web Development Template
> A simple project that aims to build a Dockerized template environment for web development
> A ready-to-use Docker LEMP stack template for PHP web development — configurable, secure by default, and structured for real-world projects.

![CI BUILD](https://github.com/nspalo/dockerize-web-development-template/actions/workflows/ci-build.yml/badge.svg)

## Features

- **Multi-stage PHP build** — minimal runtime image, extensions compiled separately
- **Compose overrides** — dev (hot-reload, debug ports) and prod (locked down, resource limits)
- **Single config file** — one place to change PHP version, DB version, ports, environment
- **Makefile interface** — `make help` shows all commands, no scripts to memorize
- **Secure by default** — non-root containers, security headers, hidden files blocked, no-new-privileges
- **Configurable** — PHP 8.4, MySQL 8.0, Node 20, all swappable via `config.env`

## Quick Start

```bash
Expand Down Expand Up @@ -30,7 +39,7 @@ All settings are controlled from a single file: `docker/environments/config.env`

```env
SYS_ENV=dev # Environment: dev | staging | prod
PHP_VERSION=8.3 # PHP version
PHP_VERSION=8.4 # PHP version
DB_VERSION=8.0 # MySQL version
NODE_VERSION=20 # Node.js version
APP_PORT=80 # Web server port
Expand Down
26 changes: 24 additions & 2 deletions docker/containers/nginx/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,28 @@ FROM nginx:stable-alpine
WORKDIR /var/www/html

# Copy NginX configuration
# This is crucial for making the built image self-contained and ready for production,
# even though the volume mount in docker-compose.yml will override it for local development.
COPY ./containers/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf

# Security: Run Nginx as non-root user
# 1. Create app user matching PHP container's user
# 2. Adjust ownership of directories Nginx needs to write to
# 3. Configure Nginx to run as non-root (listen on 8080 internally)
RUN addgroup -g 1000 appuser \
&& adduser -u 1000 -G appuser -s /bin/sh -D appuser \
&& chown -R appuser:appuser /var/cache/nginx \
&& chown -R appuser:appuser /var/log/nginx \
&& chown -R appuser:appuser /etc/nginx/conf.d \
&& touch /var/run/nginx.pid \
&& chown appuser:appuser /var/run/nginx.pid

# Switch to non-root user
USER appuser

# Expose unprivileged port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8080/ || exit 1

CMD ["nginx", "-g", "daemon off;"]
22 changes: 20 additions & 2 deletions docker/containers/nginx/conf.d/default.conf
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
server {
listen 80;
# Listen on unprivileged port (container runs as non-root)
# Host port mapping is handled by docker-compose
listen 8080;

index index.php index.html;
# server_name <ServerName>;

error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;

root /var/www/html/public/;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Deny access to hidden files (e.g., .env, .git)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}

location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
Expand All @@ -17,6 +35,6 @@ server {
}

location / {
try_files $uri $uri/ /index.php?$query_string;
try_files $uri $uri/ /index.php?$query_string;
}
}
9 changes: 8 additions & 1 deletion docker/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
# - Exposed debug ports (PHP, MySQL accessible from host)
# - Nginx config mounted for live editing
# - Nginx logs volume for debugging
# - HOST_UID/HOST_GID for file permission alignment
# ===========================================================================

services:
webserver:
ports:
- "${APP_PORT}:80"
- "${APP_PORT}:8080"
volumes:
- "${PATH_CONTAINERS}/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf"
- nginx_logs:/var/log/nginx
Expand All @@ -35,6 +36,12 @@ services:
- "${DB_PORT}:3306"
tty: true

composer:
user: "${HOST_UID}:${HOST_GID}"

npm:
user: "${HOST_UID}:${HOST_GID}"

volumes:
# Nginx logs
nginx_logs:
Expand Down
19 changes: 17 additions & 2 deletions docker/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,28 @@
# - Restart policies on all services
# - Resource limits to prevent runaway containers
# - Health checks for reliability
# - Read-only filesystems where possible
# - Security options (no new privileges)
# ===========================================================================

services:
webserver:
ports:
- "${APP_PORT}:80"
- "${APP_PORT}:8080"
restart: unless-stopped
read_only: true
tmpfs:
- /var/cache/nginx
- /var/run
security_opt:
- no-new-privileges:true
deploy:
resources:
limits:
memory: 256M
cpus: "0.50"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/"]
interval: 30s
timeout: 5s
start_period: 10s
Expand All @@ -35,6 +43,11 @@ services:
PHP_VERSION: "${PHP_VERSION}"
SYS_ENV: "${SYS_ENV}"
restart: unless-stopped
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
deploy:
resources:
limits:
Expand All @@ -45,6 +58,8 @@ services:

database:
restart: unless-stopped
security_opt:
- no-new-privileges:true
deploy:
resources:
limits:
Expand Down
34 changes: 0 additions & 34 deletions docker/volumes/README.md

This file was deleted.

Loading