{"id":80,"date":"2019-04-17T16:00:17","date_gmt":"2019-04-17T16:00:17","guid":{"rendered":"http:\/\/ssdnodes.billabailey.com\/2017\/06\/05\/tutorial-using-docker-and-nginx-to-host-multiple-websites\/"},"modified":"2025-05-18T13:22:15","modified_gmt":"2025-05-18T13:22:15","slug":"host-multiple-websites-docker-nginx","status":"publish","type":"post","link":"https:\/\/www.ssdnodes.com\/blog\/host-multiple-websites-docker-nginx\/","title":{"rendered":"Host Multiple Websites On One VPS With Docker And Nginx"},"content":{"rendered":"<p>Docker is an excellent tool for running multiple services on a single VPS without them interfering with each other\u2014for example, one website built on <a href=\"https:\/\/www.ssdnodes.com\/blog\/tutorial-installing-wordpress-on-lemp-and-ubuntu-16-04\/\">WordPress<\/a> and another built on Ghost or 10 Flat-File Content Managers to Help You Ditch WordPresssome other flat-file CMS. But, containerizing software leads to another problem that confuses many: How do I host multiple websites, each in a separate Docker container, from one VPS? Fortunately, with a little bit of foresight and configuring, you can use Docker and Nginx to host multiple websites from a single VPS.<\/p>\n<\/p>\n<p>By default, Docker services all listen on port 80, which would create conflicts for incoming traffic. You can change the listening port, of course, but no one wants to type in\u00a0<code>coolwebsite.com:34567<\/code>\u00a0to access their favorite site.<\/p>\n<p>What if, instead, you could use\u00a0<code>nginx<\/code>\u00a0to route traffic arriving at\u00a0<code>coolwebsite.com<\/code>\u00a0to a unique to a container listening on the\u00a0<code>34567<\/code> port, and route traffic arriving to\u00a0<code>anothercoolwebsite.net<\/code>\u00a0a second container listening to\u00a0<code>45678<\/code>?<\/p>\n<p>That's exactly what\u00a0<code>nginx-proxy<\/code>\u00a0does: it listens to port 80 (the standard HTTP port) and forwards incoming requests to the appropriate container. This is often known as a\u00a0<em>reverse proxy<\/em>, and takes advantage of Docker's\u00a0<code>VIRTUAL_HOST<\/code>\u00a0variable.<\/p>\n<p>In this tutorial, we'll set up\u00a0<code>nginx-proxy<\/code>\u00a0and learn how to use Docker and Nginx to route traffic to different containers, thereby allowing you to host multiple websites on different domains from a single website.<\/p>\n<h2>Prerequisites<\/h2>\n<ul>\n<li>An Ubuntu\/Debian\/CentOS server with Docker installed\u2014to get started with that, check out our\u00a0<a href=\"https:\/\/www.ssdnodes.com\/blog\/tutorial-getting-started-with-docker-on-your-vps\/\">getting started with Docker tutorial<\/a>.<\/li>\n<li>A non-root user with\u00a0<code>sudo<\/code>\u00a0privileges.<\/li>\n<\/ul>\n<div class=\"cta-inline\"><\/div>\n<h2>Step 1. Starting up nginx-proxy to hook Docker and Nginx together<\/h2>\n<p>To get started, let's start up the\u00a0<code>nginx-proxy<\/code>\u00a0container. This can be accomplished either by a single\u00a0<code>docker<\/code>\u00a0command, or using\u00a0<code>docker-compose<\/code>. Let's cover both.<\/p>\n<p>To get started, create a Docker network<\/p>\n<p>Before we get started, either way, we need to first create a Docker network that we will use to bridge all of these containers together.<\/p>\n<pre><code class=\"language-shell hljs\"><span class=\"hljs-meta\">$<\/span><span class=\"bash\"> docker network create nginx-proxy<\/span>\n<\/code><\/pre>\n<p>From now on, we need to ensure that we're always adding new containers to the\u00a0<code>nginx-proxy<\/code>\u00a0Docker network.<\/p>\n<h3>Installing nginx-proxy with Docker<\/h3>\n<pre><code class=\"language-shell hljs\"><span class=\"hljs-meta\">$<\/span><span class=\"bash\"> docker run <span class=\"hljs-_\">-d<\/span> -p 80:80 --name nginx-proxy --net nginx-proxy -v \/var\/run\/docker.sock:\/tmp\/docker.sock jwilder\/nginx-proxy<\/span>\n<\/code><\/pre>\n<h3>Installing nginx-proxy with docker-compose<\/h3>\n<p>First, create a new\u00a0<code>docker-compose.yml<\/code>\u00a0file in the directory of your choosing (one titled\u00a0<code>nginx-proxy<\/code>\u00a0is a good idea), and copy in the following text:<\/p>\n<pre><code>version: \"3\"\nservices:\n  nginx-proxy:\n    image: jwilder\/nginx-proxy\n    container_name: nginx-proxy\n    ports:\n      - \"80:80\"\n    volumes:\n      - \/var\/run\/docker.sock:\/tmp\/docker.sock:ro\n\nnetworks:\n  default:\n    external:\n      name: nginx-proxy\n<\/code><\/pre>\n<p>And then run the following\u00a0<code>docker-compose<\/code>\u00a0command to get started.<\/p>\n<pre><code class=\"language-shell hljs\"><span class=\"hljs-meta\">$<\/span><span class=\"bash\"> docker-compose up <span class=\"hljs-_\">-d<\/span><\/span>\n<\/code><\/pre>\n<h3>How nginx-proxy works to host multiple websites<\/h3>\n<p>As you can see from the code in both options, the container listens on port 80 and exposes the same port inside of the container. That allows all incoming traffic to flow though\u00a0<code>nginx<\/code>.<\/p>\n<p>You might be wondering what the\u00a0<code>\/var\/run\/docker.sock:\/tmp\/docker.sock<\/code>\u00a0line accomplishes. Essentially, this gives any container access to the host's Docker socket, which contains information about a variety of Docker events, such as creating a new container, or shutting one down.<\/p>\n<p>This means that every time you add a container,\u00a0<code>nginx-proxy<\/code>\u00a0sees the event through the socket, automatically creates the configuration file needed to route traffic, and restarts\u00a0<code>nginx<\/code>\u00a0to make the changes available immediately.\u00a0<code>nginx-proxy<\/code>\u00a0looks for containers with the\u00a0<code>VIRTUAL_HOST<\/code>\u00a0variable enabled, so that's critical to our operations moving forward.<\/p>\n<p>Also important to note is the\u00a0<code>--net nginx-proxy<\/code>\u00a0line in the Docker command, and the\u00a0<code>networks: default: external: name: nginx-proxy<\/code>\u00a0block in the\u00a0<code>docker-compose.yml<\/code>\u00a0file. These establish that all containers will communicate over that Docker network.<\/p>\n<h2>Step 2. Adding a container to the proxy<\/h2>\n<p>Now that we have\u00a0<code>nginx-proxy<\/code>\u00a0running, we can start adding new containers, which will be automatically picked up and configured for. Because we covered it in the last Docker tutorial, and since it's an easy implementation to try out, let's use WordPress as an example.<\/p>\n<h3>Using Docker<\/h3>\n<p>Starting a WordPress container with a basic configuration is quite easy.<\/p>\n<pre><code class=\"hljs shell\"><span class=\"hljs-meta\">$<\/span><span class=\"bash\"> docker run <span class=\"hljs-_\">-d<\/span> --name blog --expose 80 --net nginx-proxy <span class=\"hljs-_\">-e<\/span> VIRTUAL_HOST=blog.DOMAIN.TLD wordpress<\/span>\n<\/code><\/pre>\n<p>Take note of a few elements here.\u00a0<code>--expose 80<\/code>\u00a0will allow traffic to flow into the container on port 80.\u00a0<code>--net nginx-proxy<\/code>\u00a0ensures we're using the Docker network we created earlier. Finally,\u00a0<code>-e VIRTUAL_HOST=blog.DOMAIN.TLD<\/code> flags\u00a0<code>nginx-proxy<\/code>\u00a0to create the proxy information and direct traffic arriving to the correct domain.<\/p>\n<h3>Using docker-compose<\/h3>\n<p>Start by creating a 'docker-compose.yml' file in an empty directory and copying in the following.<\/p>\n<pre><code>version: \"3\"\n\nservices:\n   db_node_domain:\n     image: mysql:5.7\n     volumes:\n       - db_data:\/var\/lib\/mysql\n     restart: always\n     environment:\n       MYSQL_ROOT_PASSWORD: PASSWORD\n       MYSQL_DATABASE: wordpress\n       MYSQL_USER: wordpress\n       MYSQL_PASSWORD: PASSWORD\n     container_name: wordpress_db\n\n   wordpress:\n     depends_on:\n       - db_node_domain\n     image: wordpress:latest\n     expose:\n       - 80\n     restart: always\n     environment:\n       VIRTUAL_HOST: blog.DOMAIN.TLD\n       WORDPRESS_DB_HOST: db_node_domain:3306\n       WORDPRESS_DB_USER: wordpress\n       WORDPRESS_DB_PASSWORD: PASSWORD\n     container_name: wordpress\nvolumes:\n    db_data:\n\nnetworks:\n  default:\n    external:\n      name: nginx-proxy\n<\/code><\/pre>\n<p>Again, take note of the\u00a0<code>expose<\/code>\u00a0and\u00a0<code>environment: VIRTUAL_HOST<\/code>\u00a0options within the file. Also, the\u00a0<code>networks<\/code>\u00a0option at the bottom is necessary to allow this container to &quot;speak&quot; with\u00a0<code>nginx-proxy<\/code>.<\/p>\n<p>Also, take note of the line that begins with <code class=\"language-yaml\">db_node_domain<\/code>. If you are going to host multiple WordPress blogs using this method, you need to create unique database names for each. You should replace <code class=\"language-yaml\">db_node_domain<\/code> with your preferring naming scheme. You also need to update two other fields with this chosen name. First:<\/p>\n<p><code class=\"language-yaml\">depends_on:<\/code><\/p>\n<p><code class=\"language-yaml\">\u00a0 - db_node_domain<\/code><\/p>\n<p>And second:<\/p>\n<p><code class=\"language-yaml\">WORDPRESS_DB_HOST: db_node_domain:3306<\/code><\/p>\n<p>Once the configuration file is set up, you can run the <code>docker-compose up -d<\/code> command. As long as your DNS is set up to route traffic properly, things should work correctly.<\/p>\n<p>From here, you can start up any number of additional WordPress site\u2014or any type of service, for that matter\u2014and have them be automatically added to the\u00a0<code>nginx-proxy<\/code>\u00a0network. This Docker and Nginx configuration is pretty infinitely extensible, limited only by the VPS resources available to you.<\/p>\n<h2>Additional resources to host multiple websites<\/h2>\n<p>Of course, be sure to check out\u00a0<a href=\"https:\/\/github.com\/jwilder\/nginx-proxy\" target=\"_blank\" rel=\"noopener\">the extensive documentation<\/a>\u00a0for\u00a0<code>nginx-proxy<\/code>\u00a0to learn more about how you can configure some more complex proxies between Docker and Nginx, such as those using SSL, with multiple ports, or multiple networks.<\/p>\n<p>We haven't tested it out yet, but there's a\u00a0<a href=\"https:\/\/github.com\/JrCs\/docker-letsencrypt-nginx-proxy-companion\" target=\"_blank\" rel=\"noopener\">&quot;companion&quot;<\/a>\u00a0to\u00a0<code>nginx-proxy<\/code>\u00a0called\u00a0<code>letsencrypt-nginx-proxy-companion<\/code>\u00a0that allows for the creation\/renewal of Let's Encrypt certificates automatically directly alongside the proxy itself.<\/p>\n<p>Just another example of the really cool things that you can do with Docker!<br \/>\n<code>Thanks much to our sharp-eyed reader John! He pointed out how we can improve our docker-compose files by creating unique database names for each instance of WordPress!<\/code><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Docker is an excellent tool for running multiple services on a single VPS without them interfering with each other\u2014for example, one website built on Wordpress and another built on Ghost. But, containerizing software leads to another problem that confuses many: How do I host multiple websites, each in a separate Docker container, from one VPS?<\/p>\n","protected":false},"author":20,"featured_media":2128,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[18,30],"tags":[182,211],"class_list":["post-80","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","category-tutorials","tag-docker","tag-nginx"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/80","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/comments?post=80"}],"version-history":[{"count":3,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/80\/revisions"}],"predecessor-version":[{"id":13022,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/80\/revisions\/13022"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media\/2128"}],"wp:attachment":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media?parent=80"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/categories?post=80"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/tags?post=80"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}