├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── certs └── .gitignore ├── conf ├── .gitignore └── custom.conf ├── dhparam └── .gitignore ├── html └── .gitkeep ├── scripts ├── .env.example ├── .gitignore ├── build.sh ├── docker-compose.yml ├── down.sh ├── restart.sh ├── self-signed-perms │ ├── Dockerfile │ └── entrypoint.sh └── start.sh └── vhost └── .gitignore /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | max_line_length = 180 11 | tab_width = 4 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ion Ghițun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nginx-Proxy Stack 2 | 3 | This project provides a Docker Compose setup for [nginx-proxy](https://github.com/nginx-proxy/nginx-proxy) with optional support for [acme-companion](https://github.com/nginx-proxy/acme-companion) and [self-signed-proxy-companion](https://github.com/sebastienheyd/docker-self-signed-proxy-companion). It enables easy development of multiple 4 | projects locally with virtual hosts using Docker and is also ready for live deployment. 5 | 6 | ## Prerequisites 7 | 8 | - Docker Engine & Docker Compose 9 | - Git 10 | - Linux or Windows WSL2 11 | 12 | ## Installation 13 | 14 | 1. **Clone the repository** 15 | ```bash 16 | git clone https://github.com/ionghitun/nginx-proxy.git 17 | cd nginx-proxy 18 | ``` 19 | 2. **Copy and configure environment variables** 20 | ```bash 21 | cp scripts/.env.example scripts/.env 22 | export USER_ID=$(id -u) 23 | export GROUP_ID=$(id -g) 24 | # Edit other variables in scripts/.env as needed 25 | ``` 26 | 3. **Create global nginx-proxy network** 27 | ```bash 28 | docker network create nginx-proxy 29 | ``` 30 | 4. **Start the project** 31 | ```bash 32 | sh scripts/start.sh 33 | ``` 34 | 35 | ## Configuration 36 | 37 | **Conf Configuration**: Edit `conf/custom.conf` 38 | 39 | ### Compose Profiles 40 | 41 | - acme: Enables automatic SSL certificate generation using Let's Encrypt via acme-companion. 42 | - self-signed: Enables self-signed SSL certificate generation for local development via self-signed-proxy-companion. 43 | - leave COMPOSE_PROFILES empty to run only nginx-proxy without SSL support. 44 | 45 | ### Example: Using Virtual Hosts in Containers 46 | 47 | To enable virtual hosting for your containers, set port, environment and network: 48 | 49 | ```yaml 50 | services: 51 | ... 52 | nginx: 53 | ... 54 | ports: 55 | - 80 56 | - ... 57 | environment: 58 | VIRTUAL_HOST: example.com 59 | VIRTUAL_PORT: 80 60 | LETSENCRYPT_HOST: example.com 61 | LETSENCRYPT_EMAIL: mail@example.com 62 | ... 63 | networks: 64 | - nginx-proxy 65 | - ... 66 | ``` 67 | 68 | For multiple domains or subdomains: 69 | 70 | ```yaml 71 | VIRTUAL_HOST: example.com,sub.example.com,example2.com 72 | VIRTUAL_PORT: 80 73 | LETSENCRYPT_HOST: example.com,sub.example.com,example2.com 74 | LETSENCRYPT_EMAIL: mail@example.com 75 | ``` 76 | 77 | When using the self-signed companion, add extra the `SELF_SIGNED_HOST` environment variable: 78 | 79 | ```yaml 80 | environment: 81 | SELF_SIGNED_HOST: example.com 82 | ``` 83 | 84 | ### Trusting Self-Signed Certificates 85 | 86 | To avoid browser warnings like "Your connection is not private" when using self-signed certificates, refer to 87 | the [self-signed-proxy-companion](https://github.com/sebastienheyd/docker-self-signed-proxy-companion) documentation for instructions on trusting these certificates in your local 88 | environment. 89 | 90 | ### Using Certificates with Vite Server 91 | 92 | To use SSL certificates in applications like Vite, mount the certs directory as a read-only volume in your application container: 93 | To have permissions to use, hosts must end in `.local` 94 | 95 | ```yaml 96 | volumes: 97 | - /path/to/nginx-proxy/certs:/path/in/container/certs:ro 98 | ``` 99 | 100 | ## Available scripts 101 | 102 | ```bash 103 | ./scripts/start.sh # Start the containers 104 | ./scripts/down.sh # Stop the containers 105 | ./scripts/build.sh # Build or rebuild the containers 106 | ./scripts/restart.sh # Restart the containers 107 | ``` 108 | 109 | ## Troubleshooting 110 | 111 | - **Permission Issues**: Ensure `USER_ID` and `GROUP_ID` in `scripts/.env` match your host user IDs. 112 | - **Docker Issues**: For older versions you might want to remove `COMPOSE_BAKE` from `.env`. 113 | - **Docker Compose Issues**: Please update and ensure you can use `docker compose`, not old version `docker-compose` 114 | 115 | ## License 116 | 117 | This project is licensed under the MIT License. See [LICENSE](LICENSE) for details. 118 | 119 | ## Contributing 120 | 121 | Contributions are welcome! Please open issues or submit pull requests following the repository guidelines. 122 | 123 | _Happy Coding_ 124 | -------------------------------------------------------------------------------- /certs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /conf/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !custom.conf 4 | -------------------------------------------------------------------------------- /conf/custom.conf: -------------------------------------------------------------------------------- 1 | client_max_body_size 4G; 2 | client_body_buffer_size 32M; 3 | 4 | fastcgi_buffers 256 1024k; 5 | fastcgi_buffer_size 512k; 6 | fastcgi_busy_buffers_size 1024k; 7 | fastcgi_connect_timeout 1800s; 8 | fastcgi_send_timeout 1800s; 9 | fastcgi_read_timeout 1800s; 10 | 11 | server_tokens off; 12 | 13 | proxy_buffers 256 1024k; 14 | proxy_buffer_size 512k; 15 | proxy_busy_buffers_size 1024k; 16 | proxy_send_timeout 1800s; 17 | proxy_read_timeout 1800s; 18 | 19 | send_timeout 1800s; 20 | 21 | keepalive_requests 512; 22 | 23 | access_log /var/log/nginx/access.log; 24 | error_log /var/log/nginx/error.log warn; 25 | -------------------------------------------------------------------------------- /dhparam/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /html/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionghitun/nginx-proxy/ab6ee2c084e4b068b42a46f541ff1398be95f2bc/html/.gitkeep -------------------------------------------------------------------------------- /scripts/.env.example: -------------------------------------------------------------------------------- 1 | #name of the stack 2 | COMPOSE_PROJECT_NAME=nginx-proxy 3 | 4 | #for better build performance 5 | COMPOSE_BAKE=true 6 | 7 | #images versions to use 8 | NGINX_PROXY_VERSION=latest 9 | ACME_COMPANION_VERSION=latest 10 | SELF_SIGNED_VERSION=latest 11 | 12 | #compose profile, leave empty to run only nginx-proxy 13 | #possible values: acme, self-signed 14 | COMPOSE_PROFILES=acme 15 | 16 | #output of ~id -u and ~id -g commands 17 | USER_ID=1000 18 | GROUP_ID=1000 19 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd scripts || exit 3 | 4 | echo 5 | printf "Do you want to update images before rebuilding? (y/n) [default: y]: " 6 | read UPDATE_IMAGES 7 | UPDATE_IMAGES=${UPDATE_IMAGES:-y} 8 | 9 | COMPOSE_PROFILES=$(sed -n 's/^COMPOSE_PROFILES=//p' .env) 10 | 11 | if [ "$UPDATE_IMAGES" = "y" ] || [ "$UPDATE_IMAGES" = "Y" ]; then 12 | echo 13 | echo "===== Updating images... =====" 14 | echo 15 | 16 | NGINX_PROXY_VERSION=$(sed -n 's/^NGINX_PROXY_VERSION=//p' .env) 17 | ACME_COMPANION_VERSION=$(sed -n 's/^ACME_COMPANION_VERSION=//p' .env) 18 | SELF_SIGNED_VERSION=$(sed -n 's/^SELF_SIGNED_VERSION=//p' .env) 19 | 20 | docker pull "nginxproxy/nginx-proxy:$NGINX_PROXY_VERSION" 21 | 22 | if [ "$COMPOSE_PROFILES" = "acme" ]; then 23 | docker pull "nginxproxy/acme-companion:$ACME_COMPANION_VERSION" 24 | elif [ "$COMPOSE_PROFILES" = "self-signed" ]; then 25 | docker pull "sebastienheyd/self-signed-proxy-companion:$SELF_SIGNED_VERSION" 26 | docker pull alpine:latest 27 | fi 28 | fi 29 | 30 | echo 31 | echo "===== Building and starting containers... =====" 32 | echo 33 | 34 | if command -v docker-compose >/dev/null 2>&1; then 35 | docker-compose build --no-cache 36 | docker-compose up -d 37 | else 38 | docker compose build --no-cache 39 | docker compose up -d 40 | fi 41 | 42 | echo 43 | echo "===== Done! =====" 44 | echo 45 | -------------------------------------------------------------------------------- /scripts/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | proxy-nginx-proxy: 3 | container_name: nginx-proxy-nginx-proxy 4 | image: nginxproxy/nginx-proxy:${NGINX_PROXY_VERSION} 5 | restart: unless-stopped 6 | environment: 7 | ENABLE_IPV6: 'true' 8 | ports: 9 | - "80:80" 10 | - "443:443" 11 | volumes: 12 | - ../conf:/etc/nginx/conf.d 13 | - ../vhost:/etc/nginx/vhost.d 14 | - ../html:/usr/share/nginx/html 15 | - ../dhparam:/etc/nginx/dhparam 16 | - ../certs:/etc/nginx/certs:ro 17 | - /var/run/docker.sock:/tmp/docker.sock:ro 18 | 19 | proxy-acme-companion: 20 | container_name: nginx-proxy-acme-companion 21 | image: nginxproxy/acme-companion:${ACME_COMPANION_VERSION} 22 | restart: unless-stopped 23 | depends_on: 24 | - proxy-nginx-proxy 25 | volumes: 26 | - ../vhost:/etc/nginx/vhost.d 27 | - ../html:/usr/share/nginx/html 28 | - ../dhparam:/etc/nginx/dhparam 29 | - ../certs:/etc/nginx/certs:rw 30 | - /var/run/docker.sock:/var/run/docker.sock:ro 31 | environment: 32 | NGINX_PROXY_CONTAINER: proxy-nginx-proxy 33 | FILES_UID: ${USER_ID} 34 | FILES_GID: ${GROUP_ID} 35 | FILES_PERMS: 644 36 | profiles: 37 | - acme 38 | 39 | proxy-self-signed-companion: 40 | container_name: nginx-proxy-self-signed-companion 41 | image: sebastienheyd/self-signed-proxy-companion:${SELF_SIGNED_VERSION} 42 | restart: unless-stopped 43 | depends_on: 44 | - proxy-nginx-proxy 45 | volumes: 46 | - ../certs:/etc/nginx/certs:rw 47 | - /var/run/docker.sock:/var/run/docker.sock:ro 48 | environment: 49 | NGINX_PROXY_CONTAINER: proxy-nginx-proxy 50 | profiles: 51 | - self-signed 52 | 53 | proxy-self-signed-perms: 54 | container_name: nginx-proxy-self-signed-perms 55 | build: 56 | context: ./ 57 | dockerfile: self-signed-perms/Dockerfile 58 | restart: unless-stopped 59 | volumes: 60 | - ../certs:/etc/nginx/certs:rw 61 | environment: 62 | - USER_ID=${USER_ID} 63 | - GROUP_ID=${GROUP_ID} 64 | profiles: 65 | - self-signed 66 | 67 | networks: 68 | default: 69 | external: true 70 | name: nginx-proxy 71 | -------------------------------------------------------------------------------- /scripts/down.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo 3 | echo "===== Stopping... =====" 4 | echo 5 | 6 | cd scripts || exit 7 | if command -v docker-compose >/dev/null 2>&1; then 8 | docker-compose down 9 | else 10 | docker compose down 11 | fi 12 | 13 | echo 14 | echo "===== Done! =====" 15 | echo 16 | -------------------------------------------------------------------------------- /scripts/restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo 3 | echo "===== Restarting... =====" 4 | echo 5 | 6 | cd scripts || exit 7 | if command -v docker-compose >/dev/null 2>&1; then 8 | docker-compose restart 9 | else 10 | docker compose restart 11 | fi 12 | 13 | echo 14 | echo "===== Done! =====" 15 | echo 16 | -------------------------------------------------------------------------------- /scripts/self-signed-perms/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | RUN apk add --no-cache inotify-tools bash 4 | 5 | COPY self-signed-perms/entrypoint.sh /entrypoint.sh 6 | RUN chmod +x /entrypoint.sh 7 | 8 | ENTRYPOINT ["/entrypoint.sh"] 9 | CMD ["tail", "-f", "/dev/null"] 10 | -------------------------------------------------------------------------------- /scripts/self-signed-perms/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "[startup] Fixing existing .local certs..." 6 | find /etc/nginx/certs -type f -name '*.local.crt' -exec chown "${USER_ID}:${GROUP_ID}" {} + 7 | find /etc/nginx/certs -type f -name '*.local.key' -exec chown "${USER_ID}:${GROUP_ID}" {} + 8 | 9 | echo "[startup] Watching for new .local certs..." 10 | inotifywait -m -e create -e modify --format '%w%f' /etc/nginx/certs | while read -r file; do 11 | case "$file" in 12 | *.local.crt) 13 | echo "[watcher] Detected new .local.crt file, setting ownership to ${USER_ID}:${GROUP_ID}" 14 | chown "${USER_ID}:${GROUP_ID}" "$file" 15 | ;; 16 | *.local.key) 17 | echo "[watcher] Detected new .local.key file, setting ownership to ${USER_ID}:${GROUP_ID}" 18 | chown "${USER_ID}:${GROUP_ID}" "$file" 19 | ;; 20 | esac 21 | done 22 | -------------------------------------------------------------------------------- /scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo 3 | echo "===== Starting... =====" 4 | echo 5 | 6 | cd scripts || exit 7 | if command -v docker-compose >/dev/null 2>&1; then 8 | docker-compose up -d 9 | else 10 | docker compose up -d 11 | fi 12 | 13 | echo 14 | echo "===== Done! =====" 15 | echo 16 | -------------------------------------------------------------------------------- /vhost/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | --------------------------------------------------------------------------------