Using docker-compose to add web apps: The self-hosting handbook

Welcome to the third page of a handbook on self-hosting. Begin here. On this page, we'll be using docker-compose to add web apps and containers to our self-hosting stack.
Topics covered on this page
- A recap of environment variables and ports
- Example: Adding Gitea to your self-hosting stack
- Example: Adding FreshRSS to your self-hosting stack
- How does this actually work?
What's the BEST DEAL in cloud hosting?
Develop at hyperspeed with a Performance VPS from SSD Nodes. We DOUBLED the amount of blazing-fast NVMe storage on our most popular plan and beefed up the CPU offering on these plans. There's nothing else like it on the market, at least not at these prices.
Score a 16GB Performance VPS with 160GB of NVMe storage for just $99/year for a limited time!
A recap of environment variables and ports
On the previous page of this self-hosting handbook, we walked through your docker-compose.yml
file and I tried to explain how to specify different environment variables that will both allow our orchestration containers to grab SSL certificates from Let's Encrypt, and allow all the containers to communicate with each other as needed.
I have a feeling some of these variables and configurations might still be a little confusing, so let's take a moment to recap the important ones and how to set them up correctly in the future. Throughout the remainder of this handbook, I will include docker-compose
service blocks that I've tested to work with this self-hosting stack, but there are likely other containers you will want to add to your stack, and you'll only be able to do that if you understand how these environment variables work.
As I mentioned on the previous page, there are three critical environment variables: VIRTUAL_HOST
, LETSENCRYPT_HOST
, and LETSENCRYPT_EMAIL
. Let me quote myself from that page.
There are three essential environment variables to set here: the VIRTUAL_HOST
, the LETSENCRYPT_HOST
, and the LETSENCRYPT_EMAIL
.
The LETSENCRYPT_EMAIL
variable is simplest to explain: Replace the EMAIL
variable with your email.
The VIRTUAL_HOST
and LETSENCRYPT_HOST
both reference the domain at which you'll access the Portainer service using your browser. Both these variables must be set on every Docker container you want to access via the reverse proxy.
There are a few other environment variables that you may need to employ.
VIRTUAL_PORT
To get started, here's what the nginx-proxy
documentation says about VIRTUAL_PORT
:
If your container exposes multiple ports, nginx-proxy will default to the service running on port 80. If you need to specify a different port, you can set a VIRTUAL_PORT env var to select a different one. If your container only exposes one port and it has a VIRTUAL_HOST env var set, that port will be selected.
By default, nginx-proxy
will assume that your container will be listening for traffic on port 80. If the ports
section in the docker-compose
configuration reads something like - "667:80"
, then you don't need to use VIRTUAL_PORT
. In that case, the Docker containing will be listening to traffic on port 80, and nginx-proxy
will take care of the rest.
But, what if the ports
section was something like - "3000:5000
? This means that the Docker container is listening for traffic on port 5000
. You must use VIRTUAL_PORT
in this case to force nginx-proxy
to send traffic to the correct port, and not the 80
default.
If this doesn't quite make sense yet, we'll see this VIRTUAL_PORT
variable in action when adding Gitea to our stack in the next step.
Example: Adding Gitea to your self-hosting stack using docker-compose
Your Portainer container should be working just fine, but you didn't get all this way to have a Docker monitoring solution and no Docker containers! Let's get started with a popular choice for self-hosting: an alternative to hosted (and often paid) Git hosting services like GitHub.
By adding Gitea to your self-hosting stack, you'll be able to host your own version-controlled software, whether you keep it private or share it with the world!
Add a new DNS record
Gitea will require its own subdomain, just as Portainer does at docker.DOMAIN.TLD
. Head back into your DNS configuration and create a new A record at the subdomain of your choosing. I tend to pick gitea
, but it's entirely up to you.
Add to the docker-compose.yml file
Let's get right into it. Here's the entire block to add to the services
section of your docker-compose.yml
file.
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
environment:
- VIRTUAL_HOST=gitea.DOMAIN.TLD
- LETSENCRYPT_HOST=gitea.DOMAIN.TLD
- LETSENCRYPT_EMAIL=EMAIL
- VIRTUAL_PORT=3000
- ROOT_URL=https://gitea.DOMAIN.TLD
- DOMAIN=gitea.DOMAIN.TLD
- PROTOCOL=http
- USER_UID=1000
- USER_GID=1000
volumes:
- ./gitea:/data
ports:
- "5000:3000"
- "222:22"
networks:
- proxy-tier
- default
At first glance, this configuration is far more complicated than Portainer. While that may be true to an extent, much of this should be familiar to you. The first three environment variables are expected, and you can see the VIRTUAL_PORT
variable as well. We're telling nginx-proxy
that Gitea will be listening for traffic on port 3000, which you can see clarified down in the ports
section as well: `- "5000:3000".
The remainder of the environment variables—ROOT_URL
, DOMAIN
, PROTOCOL
, USER_UID
, and USER_GID
—are Gitea-specific. Instead of informing nginx-proxy
, these variables tell Gitea how it should run. Simply the other DOMAIN
and TLD
variables with your domain and TLD, and leave the rest as-is.
Run docker-compose up -d
Whenever you add to or otherwise change up your docker-compose.yml
file, all you need to do to deploy these changes is run docker-compose up -d
again. You should see output similar to the following:
$ docker-compose up -d
Pulling gitea (gitea/gitea:latest)...
latest: Pulling from gitea/gitea
911c6d0c7995: Pull complete
f36b1d28f2ac: Pull complete
d73c2cfa601e: Pull complete
744db10b7a3b: Pull complete
03cb1114429b: Pull complete
a28e30c2301a: Pull complete
Digest: sha256:464544cf5a7e8adf1430002a6b38076d5b5be2563363c3227144b2ae68b9a7e3
Status: Downloaded newer image for gitea/gitea:latest
Creating gitea ...
proxy is up-to-date
portainer is up-to-date
Creating gitea ... done
Hop over to your browser and navigate to the URL you specified via the DNS record and in your docker-compose.yml
file. You'll see a fully-encrypted, self-hosted Gitea installation waiting for you! You can create your user and get started right away.
Example: Adding FreshRSS to your self-hosting stack using docker-compose
Another widespread use of self-hosting is an RSS reader. I've covered this topic in the past, and have since figured out an effortless way to add FreshRSS to this stack.
As with the previous example, start by adding a new A record to your DNS. Then, add the following to your docker-compose.yml
file:
freshrss:
image: linuxserver/freshrss
container_name: freshrss
restart: unless-stopped
environment:
- VIRTUAL_HOST=feed.DOMAIN.TLD
- LETSENCRYPT_HOST=feed.DOMAIN.TLD
- [email protected]
- PGID=1001
- PUID=1000
volumes:
- ./freshrss:/config
ports:
- "667:80"
networks:
- proxy-tier
- default
Run docker-compose up -d
again to refresh your self-hosting stack.
$ docker-compose up -d
Pulling freshrss (linuxserver/freshrss:)...
latest: Pulling from linuxserver/freshrss
94c34ef7d9ee: Pull complete
e169d46472b9: Pull complete
6f85777a9276: Pull complete
6359afc496ca: Pull complete
945860b71d75: Pull complete
db1f674ef8a6: Pull complete
6e560e43af06: Pull complete
Digest: sha256:7be62e52282273f672f28dec26979ac64f53a4d14b244c67875b2c7bb8010278
Status: Downloaded newer image for linuxserver/freshrss:latest
gitea is up-to-date
Creating freshrss ...
proxy is up-to-date
portainer is up-to-date
Creating freshrss ... done
Head over to the URL you specified, and a new FreshRSS installation will be waiting for your favorite feeds!
How does this actually work?
Now that you have seen some examples of how to add more Docker containers to this self-hosting stack, let's take a moment to examine precisely how this system works together. Here's the process, simplified into a handful of steps:
- You add a new service to
docker-compose.yml
file. - You then run
docker-compose up -d
. - The new Docker container(s) are downloaded and started.
- A service called
docker-gen
, running inside ofnginx-proxy
, recognizes that a new Docker container has started via the Docker sockets file (/var/run/docker.sock
). Because this container has theVIRTUAL_HOST
environment variable set,nginx-proxy
knows it should initialize it as part of the reverse proxy network. - The
nginx-proxy
container creates new reverse proxy Nginx rules to route traffic coming into the VPS to the appropriate place. - The
letsencrypt-nginx-proxy-companion
also recognizes that a new Docker container has started via Docker sockets, and because it has theLETSENCRYPT_HOST
environment variable set to a real domain,letsencrypt-nginx-proxy-companion
knows to request a new SSL certificate from Let's Encrypt. letsencrypt-nginx-proxy-companion
andnginx-proxy
coordinate to install the new SSL certificate and enable HTTPS traffic.letsencrypt-nginx-proxy-companion
checks the existing SSL certificates every 3600 seconds (every 60 minutes) to verify if any are expiring soon. If so, it will register the SSL certificate again, ensuring traffic is always encrypted.
The underlying functionality is far, far more complicated than this numbered list suggests, but the beauty of this setup is that we don't have to worry about it. The developers have abstracted much of the headache and complexity away from us, giving us full control of our self-hosting stack while making it relatively easy to use.
Bookmark this guide and follow us on Twitter or Mastodon to get updates. Or, you can subscribe to the weekly Serverwise newsletter, where I’ll let you know as soon as this guide expands.
[cta]Table of contents
-
Self-hosting with Docker: The definitive handbook
-
Self-Hosting Handbook: A Docker-Compose Tutorial
-
Using docker-compose to add web apps: The self-hosting handbook
-
Self-hosting administration: The self-hosting handbook
-
Self-Hosting Nextcloud with Docker: Self-hosting handbook
-
Smart VPS monitoring with NetData and Docker
-
Docker Networking — Done the Right way!
Like what you saw? Subscribe to our weekly newsletter.