How to customize nginx config in a docker image
When hosting websites with docker (and proxying via Caddy), I often need to tweak the nginx config. This how-to guide will walk you through the steps you can take to accomplish this.
0. Setup
For this tutorial, here’s my setup:
Dockerfile
FROM nginx:alpine
COPY public/ /usr/share/nginx/html
The Dockerfile is in source code directory and we have another directory called nginx
where we can store customized config files.
Build image:
$ docker build ./ --tag ahoog42/www.stl-cpr.com:latest
Run container:
$ docker-compose -f docker-compose-local.yml up
If you already know where the file exists, you can just skip to step 4. docker cp
file. However, I found the follow technique very useful for exploring an image, debugging, etc. so wanted to share.
1. Determine your container
$ docker container ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
607755f91316 ahoog42/www.stl-cpr.com:latest "/docker-entrypoint.…" 15 hours ago Up 34 minutes 0.0.0.0:80->80/tcp wwwstl-cprcom-nginx-1
2. Shell into your container
If your image doesn’t have ash
installed, try sh
for the shell. If that works, you can poke around the filesystem (try ls /bin
) to see what’s available.
$ docker exec -it wwwstl-cprcom-nginx-1 /bin/ash
/ #
3. Copy nginx config files
For nginx-aline
(and probably for most nginx images), the config files are stored in /etc/nginx
:
/ # cd /etc/nginx
/etc/nginx # ls
conf.d fastcgi.conf fastcgi_params mime.types modules nginx.conf scgi_params uwsgi_params
The two files I’ve customized in the past are:
- main nginx config at
/etc/nginx/nginx.conf
- default server at
/etc/nginx/conf.d/default.conf
nginx.conf
/etc/nginx # cat nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
default.conf
/etc/nginx # cat conf.d/default.conf
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
4. docker cp
We’ll now use docker cp
to copy the file(s) from the container to our source code directory so we can modify them as needed and then incorporate them into the build process:
$ docker cp wwwstl-cprcom-nginx-1:/etc/nginx/nginx.conf ./nginx
Successfully copied 2.56kB to /Users/hiro/git/www.stl-cpr.com/nginx
$ docker cp wwwstl-cprcom-nginx-1:/etc/nginx/conf.d/default.conf ./nginx
Successfully copied 2.56kB to /Users/hiro/git/www.stl-cpr.com/nginx
You can now modify the files as you see fit. For example, while developing a static website that used javascript to submit a contact format (effectively submitted a form to a static page), nginx would throw the following error:
405 Not Allowed
You can read up more on StackOverflow but there is a hack solution (which I was OK with in development) that would just return a 200
instead of a 405
and the javascript would happy proceed. So just after the error_page 404
directive, I added the following (which I also removed in prod):
# hack to allow posts on static pages to work
# remove in production
error_page 405 =200 $uri;
5. Update Dockerfile to copy custom config
We can now update our Dockerfile to use our customized config files:
FROM nginx:alpine
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY public/ /usr/share/nginx/html
Then just rebuild you docker image, push to production and you image will run with your updated config!