Hosting Multiple Projects on a Single Server Instance

In a bid to save cost, I set out on hosting different projects on a single server, the process wasn't really all smooth. As always, I have written this article to save you time whenever you decide to implement something like this. Hosting projects on one server optimizes resources but also centralizes the control of different applications. Let's embark on a detailed exploration of how to effectively host multiple projects on a single server instance, using Digital Ocean's droplets as an example. This approach leverages Docker and Nginx, providing a robust environment for your web applications.

Docker

The Setup

Imagine a server directory structured as follows:

  • shared
  • app-one
  • app-two

Each folder serves its unique purpose, where shared contains global configurations and app-one and app-two house individual project files.

The Shared Directory

Within the shared directory, a docker-compose file sets the stage for our Nginx service. This setup involves specifying the Nginx image, configuring port mappings, and defining volume mounts. This Nginx container is only used tp proxy pass to our various apps.

Here's a simplified version of the Docker-compose configuration:


Read also : Deploy Laravel App on DigitalOcean with Ploi.


version: "3"

services:
    nginx:
        #    This Nginx serves as only as a reverse proxy for the applications hosted on this server.
        image: 'nginx:1.25-alpine'
        ports:
            - '80:80'
        volumes:
            - ./nginx/nginx.conf:/etc/nginx/nginx.conf
            - ./nginx/conf.d/:/etc/nginx/conf.d/

In the nginx.conf file, a crucial line includes all configurations from the conf.d directory, allowing for separate domain configurations for each project.

include /etc/nginx/conf.d/*.conf;

Nginx Configuration

For each domain, a dedicated configuration file within nginx/conf.d/ directs traffic appropriately. A sample configuration might look like this:

server {

    server_name api.foo.test api.bar.com;

    #IP address below should be IPV4 address of the server running the application
    #or IP address of the WSL IP gotten with "hostname -I" (172.25.42.230)
    #and external port of the application
    
    location / {
        proxy_pass http://172.25.42.230:8000;
    }

}

The IP address specified in the proxy_pass, should be IPV4 address of the server on which the application is hosted and the external port of the application's Nginx. If you are using WSL for testing, you can get the IP address of your WSL, by opening an ubuntu terminal on WSL and run:

hostname -I

Modifying the etc/hosts file is key to resolving the domain/domains name specify in the server_name above. The hosts files can be found at /etc/hosts in Linux and /Windows/System32/drivers/etc/hosts .You should add these in your hosts file to resolve the domains:

127.0.0.1	api.foo.test
127.0.0.1	api.bar.com

Application Setup

Within each application directory (e.g., app-one), a docker-compose.yml file describes the specific services, networks, and dependencies needed for that project. For PHP-FPM, ensure it runs on port 9000, aligning with the Nginx configuration:


Read also : Things to leave out of your resume.


version: "3"
services:

    mysql:
        image: 'mysql/mysql-server:8.0'
        ports:
            - '${DB_PORT}:${DB_PORT}'
        environment:
            MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ROOT_HOST: '%'
            MYSQL_TCP_PORT: '${DB_PORT}'
            MYSQL_DATABASE: '${DB_DATABASE}'
            MYSQL_USER: '${DB_USERNAME}'
            MYSQL_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ALLOW_EMPTY_PASSWORD: 0
        volumes:
            #APP_NAME is used to avoid conflicts with other projects
            - "~/.docker-volumes/${COMPOSE_PROJECT_NAME}/mysql:/var/lib/mysql"
            - "./docker/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh"
            - "./docker/mysql/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf"
        networks:
            - backend
        healthcheck:
            test: [ "CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}" ]
            retries: 3
            timeout: 5s

    php:
        build:
            context: ./docker/php
            args:
                APP_ENV: '${APP_ENV}'
        volumes:
            - "./:/var/www/html"
            # Do not add any configuration file here, it will crash app
        networks:
            - backend
        depends_on:
            - mysql

    nginx:
        image: 'nginx:1.25-alpine'
        ports:
            - '8000:80'
        volumes:
            - ./:/var/www/html
            - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
            - ./docker/nginx/conf.d/:/etc/nginx/conf.d/
        networks:
            - backend
        depends_on:
            - php

    phpmyadmin:
        build: ./docker/phpmyadmin
        ports:
            - '${PMA_EXTERNAL_PORT}:80'
        environment:
            #https://docs.phpmyadmin.net/en/release_5_1_4/setup.html#docker-environment-variables
            #https://docs.phpmyadmin.net/en/release_5_1_4/setup.html#customizing-configuration-file-using-docker-compose
            PMA_ARBITRARY: 0 #Don't ask for server name
            PMA_HOST: '${DB_CONNECTION}'
            PMA_PORT: '${DB_PORT}'
            MYSQL_USER: '${DB_USERNAME}' #Don't use 'root' as user here, it will cause errors
            MYSQL_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
            MAX_EXECUTION_TIME: 600
            MEMORY_LIMIT: '256M'
            UPLOAD_LIMIT: '2G'
        volumes:
            - "./docker/phpmyadmin/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php"
        depends_on:
            - mysql
        networks:
            - backend
    cron:
        build:
            context: ./docker/cron
        volumes:
            - "./:/var/www/html"
            # Adding crontabs volume here will crash cron. I don't know why yet
        networks:
            - backend
        depends_on:
            - mysql

networks:
    backend:
        driver: bridge
volumes:
    mysql:
        driver: local

Conclusion

By following this guide, developers can streamline their workflow, reduce resource redundancy, and enhance the scalability of their server environments. Whether you're managing two projects or twenty, this setup paves the way for a more organized and efficient development lifecycle.