[3/3] - Dockerising FreshRSS (docker-compose)


We've got our PHP and Web Dockerfiles created. Let's now tie them together using docker-compose.

First, let's create the docker-compose.yml file. I'm going to do this in the project root.

cd {project root}
touch docker-compose.yml

Whilst here, I'm also going to create a Makefile.

The purpose of the Makefile is to simplify the creation of both the running environment, and also the building of our Docker images.

touch Makefile

Before adding content to the Makefile though, let's see the Docker build commands individually.

Building the PHP Image

First, to build our PHP image:

docker build \
    --build-arg WORK_DIR=/var/www/freshrss \
    -t freshrss_php ./php

I'm splitting the command across multiple lines using the \. You do not need to do this. I'm doing this for readability.

We've covered this command already, but for clarity:

docker build is the core command to trigger a Docker build process to build an image from a given Dockerfile.

--build-arg WORK_DIR=/var/www/freshrss sets the expected build argument as defined in our Dockerfile. In this case ourDockerfileexpects aWORK_DIR` argument to be passed in. You can change this as desired, but remember to update the hardcoded value in the nginx config from the previous video.

-t freshrss_php ./php

This is actually two parts.

First, we tag the image (-t freshrss_php). This means when the image is built, it will be tagged locally as freshrss_php. When building the web image we will tag as freshrss_web. Of course, feel free to name your tags however you like.

Lastly we tell the docker build command where our Dockerfile lives.

Typically we've run this command as -t whatever ..

The use of the period (.) here means look in the current directory for the Dockerfile.

In this case, however, we say look in the ./php sub directory for the Dockerfile.

Building the Web Image

Next to build the web image:

docker build \
    -t freshrss_web ./web

Much simpler this time.

We have no build arguments, just a tag and point the docker build command at the Dockerfile in the ./web sub directory.

After running these two commands we have the expected images available locally.

Let's add these two to the Makefile:

build_php:
        @docker build \
                --build-arg WORK_DIR=/var/www/freshrss \
                -t freshrss_php ./php

build_web:
        @docker build \
                -t freshrss_web ./web

Note: Tabs are very important in a Makefile.

Composing With Docker

We have our two images. We also need a third - our database image - but fortunately, we don't need to build this ourselves.

Inside the docker-compose.yml file let's add:

version: '3'

services:

    php:
        image: freshrss_php:latest
        container_name: php

    web:
        image: freshrss_web:latest
        container_name: web
        depends_on:
            - "php"

    db:
        image: mysql:5.7.20
        container_name: db
        environment:
          - MYSQL_ROOT_PASSWORD=password
          - MYSQL_DATABASE=db
          - MYSQL_USER=dbuser
          - MYSQL_PASSWORD=dbpassword

Ok, hopefully nothing new to you here.

Our php service expects to use the local image of freshrss_php. We use the :latest tag to indicate that yup, we want to use whatever is the most recent built image.

We specify the container_name because as mentioned in the previous video, the nginx upstream.conf file depends on being able to access this container by hostname. This is against typical best practice for nginx, which suggests we use an IP instead. In Docker, however, hostnames are much easier to use, and we can pretty much guarantee our setup as per this very file.

The web service is similar to the php service.

The key difference being that we don't want the web service to be brought online before the php container is up. To ensure this never happens, we make our web service depends_on the php service. This isn't strictly necessary, but can stop real world situations where people are hammering nginx before php is available, leading to pointless errors.

Finally the db service.

We rely on a specific tag here: mysql:5.7.20. At the time of recording, 5.7.20 is the latest MySQL version available. You may find a newer version, just check Docker Hub.

The container name is important. When setting up Fresh RSS we will need to provide the name of the database server. We can simply pass in db here thanks to the container_name property.

We must also set some mandatory environment variables, or our db container won't start properly.

I'm setting my typical values here for a development environment. Yes, they aren't secure. No, I'm not going to use these same values in production :)

Is this enough to get us up and running?

Not quite.

There's two potential issues we need to fix.

Exposing Yourself

It's easy to miss, but in order to access our Dockerised services we need to expose certain ports.

It may be enough to expose the web service alone here.

Or you may also wish to expose the db, so you can poke around using your favourite client, or if you're a true geek, the CLI.

Let's add these in:

version: '3'

services:

    php:
        image: freshrss_php:latest
        container_name: php

    web:
        image: freshrss_web:latest
        container_name: web
        depends_on:
            - "php"
        ports:
            - "82:80"

    db:
        image: mysql:5.7.20
        container_name: db
        ports:
            - "33061:3306"
        environment:
          - MYSQL_ROOT_PASSWORD=password
          - MYSQL_DATABASE=db
          - MYSQL_USER=dbuser
          - MYSQL_PASSWORD=dbpassword

You don't need to use different public ports.

I do because I have tons of Docker stacks and often have multiple stacks running concurrently.

I set web to expose port 82, but really that passes through to port 80 internally.

Again, we've covered this so I'm not going to dive into it again.

What this means is I can access 0.0.0.0:82 in my browser to hit this stack.

Likewise, my db is exposed on port 33061, which means I can use e.g. SequelPro to hit 0.0.0.0:33061 and the credentials from the environment section to meddle with my database.

Keeping Your Data Around

Typically in this course we've used bind mounts over volumes.

Moving forwards I would suggest you use volumes. It's the best practice way suggested by Docker. It's also easier in many ways, if a little more confusing initially.

Let's add this config in now:

version: '3'

services:

    php:
        image: freshrss_php:latest
        container_name: php
        volumes:
            - my_freshrss_php:/var/www/freshrss

    web:
        image: freshrss_web:latest
        container_name: web
        depends_on:
            - "php"
        ports:
            - "82:80"
        volumes:
            - my_freshrss_php:/var/www/freshrss

    db:
        image: mysql:5.7.20
        container_name: db
        ports:
            - "33061:3306"
        environment:
          - MYSQL_ROOT_PASSWORD=password
          - MYSQL_DATABASE=db
          - MYSQL_USER=dbuser
          - MYSQL_PASSWORD=dbpassword
        volumes:
            - my_freshrss_db:/var/lib/mysql

volumes:
    my_freshrss_php:
    my_freshrss_db:

The volumes syntax admittedly looks weird.

As a heads up, you don't need to add a volume to your db container.

If you don't, your data will persist for the lifetime of the db container. This means if you shut your db container down, then restart it a second, a day, a week, month, or year later, it will still be there.

It won't be there, however, if you delete and recreate your db container for any reason.

If you want the data to hang around then you need to externalise it - ideally in a volume.

The php service does need to use a volume, as the web service needs access to the php services data, in order to properly function.

Again, we have covered this already in this series.

What we've added here are named volumes.

You can, of course, switch back to version 2 syntax with bind mounts. It's really your call.

At this point you can bring up your stack with docker-compose up -d, and browse to 0.0.0.0:82 and start using Fresh RSS. You will be guided through the installation process, and if at all unsure, see the video for a demo.

Remember to shut down after finishing with docker-compose down.

Updating the Makefile

We can add this command into the Makefile:

dev:
        @docker-compose down && \
                docker-compose build --pull --no-cache && \
                docker-compose \
                        -f docker-compose.yml \
                up -d --remove-orphans

This is simply a copy / paste from what I add to most every Makefile I use with Docker projects.

What this allows me to do is to run make dev from the terminal whenever I want to bring this project up. This makes every project I work with follow a set standard.

And with that, we are done.

Code For This Course

Get the code for this course.

Episodes