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:

  1. main nginx config at /etc/nginx/nginx.conf
  2. 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!