Sometime, you need to make a container accessible on your local network as if it were a device. But how to do this ?
Recently, I purchase a SSD drive for my Raspberry Pi, to replace the SD card. And since I have more space on the SSD, I have installed, with Docker, a lots of services, like OpenVPN, Pihole ... and some media server like Emby or Jellyfin, or Navidrome. I was pretty surprise that my Pi can run some media server like Emby and play musics or movies with such a good quality. I can access to my Pi from the web and access to my media server with a reverse proxy : Traefik. It's works great, but Emby or Jellyfin provide a DLNA server, so I can access to my files on my local network with a DLNA client, like my PS4. But it's doesn't work, the PS4 can't access to the DLAN server in a Docker container, because they are not on the same network.
How could I make my Emby or Jellyfin server available both on the web to acces to my medias and on my local network to access to the DLNA functionalities ?
Network_mode : host
When you read the Jellyfin's documentation to how install the server with docker, they provide a docker-compose.yml and suggest to use network_mode: host to to activate the DLNA functionnality.
version: "3"
services:
jellyfin:
image: jellyfin/jellyfin
user: 1000:1000
network_mode: "host"
restart: "unless-stopped"
volumes:
- /path/to/config:/config
- /path/to/cache:/cache
- /path/to/media:/media
The network_mode: host will bind all the ports of the host machine to the corresponding ports of the container. With this mode, the port 1900, wich is the port of the DLNA server, on the host machine will be automaticaly bind to the port 1900 of the container and the DLNA server will be available on the local network for all the DLNA client connected on the local network.
There is one drawback, you can't mix network_mode and networks in a docker-compose.yml.It's one or the other, but not both. If you don't use networks, it's the simpliest solution.
But if you have multiples containers, behind a reverse-proxy, like me, with Traeffik, you certainly have create a network to allow traefik to route your requests http to the correct container.
Macvlan driver
Despite the fact that the network_mode: host be an easy solution, Docker recommend to use the macvlan network mode instead of the host mode.
From the Docker documentation :
macvlan: Macvlan networks allow you to assign a MAC address to a container, making it appear as a physical device on your network. The Docker daemon routes traffic to containers by their MAC addresses. Using the macvlan driver is sometimes the best choice when dealing with legacy applications that expect to be directly connected to the physical network, rather than routed through the Docker host’s network stack.
Macvlan allow you to assign a MAC adress to a container, and the container will appear on the LAN Network. That's great, that's exactly what I want : make my container available for others devices on my local network.
So how to do that. You noticed it, I like to work with docker-compose,because it makes the use of containers easier than with command lines. It's like a recipe that you can easily redo at will.
Check if Macvlan is available
Check on your host machine (in my case, a raspberry pi with raspbian) if the macvlan is availabe :
lsmod | grep macv
Find your ethernet interface
Use ifconfig -a
to find your ethernet interface. eth0 or may be enp3s0 depending on your configuration.
Docker will associate your container with this interface.
Create the docker-compose.yml with a macvlan network
For my exemple, I will create an Emby server and a Jellyfin server (why choose...) But this will work for every containers you want to make available on your local network, like HomeAssistant, or Pihole.... whatever you want.
version: "2.3"
services:
emby:
image: emby/embyserver:latest
container_name: emby
restart: "unless-stopped"
volumes:
- ./programdata:/config
- ./share:/mnt/share
devices:
- /dev/dri:/dev/dri
environment:
- UID=1000 # user id of the owner of the volumes on the host machine
- GID=1000 # group id of the owner of the volumes on the host machine
networks:
lan:
# Define a static ip for the container. The containter can be accessible by others devices on the LAN network with this IP.
ipv4_address: 192.168.1.155
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
user: 1000:1000
restart: "unless-stopped"
volumes:
- ./config:/config
- ./cache:/cache
- ./share:/media
networks:
lan:
# Define a static ip for the container. The containter can be accessible by others devices on the LAN network with this IP.
ipv4_address: 192.168.1.156
networks:
lan:
name: lan
driver: macvlan
driver_opts:
parent: enp3s0 #your ethernet interface
ipam:
config:
- subnet: 192.168.1.0/16 # I use the same subnet as my LAN router.
I assume in the real life you don't need to use emby and jellyfin at the same time. But just remove one or the other according to your need.
You need to create the volumes folders on the host machine before running the containers. I put my volumes folders at the same root that my docker-compose.yml. So, for Emby, two folders, one for the configuration and one for the medias to share.
And for Jellyfin, 3 folders, one for the config, one for the cache and one for the medias.
The two image need the user id and group id for the owner of the volumes on the host machine. Use this command at the root of your volumes ls -ln
to find this infos.
The configuration for macvlan network are here :
networks:
lan:
name: lan
driver: macvlan
driver_opts:
parent: enp3s0 #your ethernet interface
ipam:
config:
- subnet: 192.168.1.0/16
Note: this options only work with docker-compose version "2.*"
I create a network called lan, every container who will use this network will use the macvlan driver and will be associate to an interface specified in parent. In this case, the ethernet interface.
Inside ipam and config I can specify some options for the network. I want my container on the same local network that my others devices so in subnet I use the same subnet that my LAN router. You can also specify a gateway and a range of ip but I don't need that.
I like to choose and specify a static IP adress for my container, so for each container, I specify the network to use and the IP I want for my container:
networks:
lan:
ipv4_address: 192.168.1.156
So everything should work, the next step is to start the container.
Start the container
To start the container, use docker-compose up -d
If you run docker-compose ps
you will see you containers running (with Ports empty) :
Name Command State Ports
----------------------------------------------------
emby /init Up
jellyfin /jellyfin/jellyfin Up
With docker network ls
you will see all the Docker networks and their drivers :
docker network ls
NETWORK ID NAME DRIVER SCOPE
895d371d01ee bridge bridge local
6d8acfc1e42c host host local
3996cdb45860 lan macvlan local
And you can inspect the macvlan network with : docker network inspect lan
:
[
{
"Name": "lan",
"Id": "3996cdb458606c3c04fd33cf6e8bf75b2f593752955a02004868675211f524a2",
"Created": "2020-08-01T17:13:22.621770479+02:00",
"Scope": "local",
"Driver": "macvlan",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "192.168.1.0/16"
}
]
},
"Internal": false,
"Attachable": true,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"8cace653673ca0d2cf24a02725557053ec9d4a1d2d720b538f0157d8a3558c6a": {
"Name": "jellyfin",
"EndpointID": "42352c4517d5a7d3f8b017ca7558920c595ef4fd7ce346cbeaa02650261d3a91",
"MacAddress": "02:42:c0:a8:01:a6",
"IPv4Address": "192.168.1.156/16",
"IPv6Address": ""
},
"db82ad8c16e029adffe43fe58fb64191b296835e3fb0bdb3d0d27a793ccef697": {
"Name": "emby",
"EndpointID": "71c6dcea6211d55c037aa4d5d81b0eb2628977c8e90d3e783e3f36a062da9585",
"MacAddress": "02:42:c0:a8:01:a5",
"IPv4Address": "192.168.1.155/16",
"IPv6Address": ""
}
},
"Options": {
"parent": "enp3s0"
},
"Labels": {
"com.docker.compose.network": "lan",
"com.docker.compose.project": "emby",
"com.docker.compose.version": "1.26.2"
}
}
]
And when I check to my router interface :
The containers are present on my LAN !
And when I try to access to this containers with the PS4 Media Player, my containers are showing up, like a classic DLNA server.
Configure Traefik
Now I have this two containers available on my local network, but I want to access it from the web. And in this case, this two containers use the same ports.
I can use a reverse-proxy like Traefik to access to my container from the web and to handle the traffic on the same ports.
You need to have your own domain name.
First I need to create a specific network, I called it web : docker network create web
Every container who use this network would be managed by traeffik.
Traefik need a configuration file, called traefik.toml.
It should look like this.
For the web interface, you must replace the credentials by you own username and password. You also need to specify your own domain name.
debug = true
logLevel = "DEBUG"
defaultEntryPoints = ["https","http"]
# API definition
# Warning: Enabling API will expose Traefik's configuration.
# It is not recommended in production,
# unless secured by authentication and authorizations
[api]
# Name of the related entry point
entryPoint = "traefik"
# Enable Dashboard
dashboard = true
# Redirect HTTPS (443) traffic to HTTP (80). This ports must be open on the modem/router
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
#Secure the dashboard with a username & password
[entryPoints.traefik]
address=":8080"
[entryPoints.traefik.auth]
[entryPoints.traefik.auth.basic]
users = ["username:password"]
# should look like this : users = ["foo:bar"]
[retry]
[docker]
endpoint = "unix:///var/run/docker.sock"
#Replace the domain name with your own domain.
domain = "www.my-domain.com"
watch = true
# Not expose container by default (to avoid "pollution")
exposedByDefault = false
usebindportip = true
[acme]
email = "foo@bar.com"
# Path to the acme.json into the traefik container (not the host) Permissions must be 0600
storage = "/etc/traefik/acme.json"
entryPoint = "https"
# For test and debugging, uncomment next line. Lets encrypt have rate limit, but there is no limit with the staging CA
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
# Create a certificate for each container (service) which have a traefik host rule defined
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
At the racine of the project, create the acme.json file: touch acme.json
and change the permissions to 0600 : chmod 0600 acme.json
Now, I need to update the docker-compose.yml and add Traefik:
version: '2.3'
services:
traefik:
image: traefik:alpine
container_name: traefik
ports:
- 80:80
- 443:443
restart: "always"
volumes:
- ./traefik.toml:/etc/traefik/traefik.toml
- ./acme.json:/etc/traefik/acme.json
- /var/run/docker.sock:/var/run/docker.sock
labels:
- "traefik.enable=true"
- "traefik.docker.network=web"
- "traefik.port=8080"
- "traefik.backend=traefik"
- "traefik.frontend.rule=Host:traefik.my-domain.com"
networks:
- web
emby:
image: emby/embyserver:latest
container_name: emby
restart: "unless-stopped"
volumes:
- ./programdata:/config
- ./share:/mnt/share
devices:
- /dev/dri:/dev/dri
environment:
- UID=1000
- GID=1000
networks:
lan:
ipv4_address: 192.168.1.155
web:
labels:
- "traefik.docker.network=web"
- "traefik.port=8096"
- "traefik.enable=true"
- "traefik.backend=emby"
- "traefik.frontend.rule=Host:emby.my-domain.com"
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
user: 1000:1000
restart: "unless-stopped"
volumes:
- ./config:/config
- ./cache:/cache
- ./share:/media
networks:
lan:
ipv4_address: 192.168.1.156
web:
labels:
- "traefik.docker.network=web"
- "traefik.port=8096"
- "traefik.enable=true"
- "traefik.backend=jellyfin"
- "traefik.frontend.rule=Host:jellyfin.my-domain.com"
networks:
web:
external: true
lan:
name: lan
driver: macvlan
driver_opts:
parent: enp3s0 #your ethernet interface
ipam:
config:
- subnet: 192.168.1.0/16
To start the container : docker-compose up -d
In your web browser, I you go to https://jellyfin.my-domain.com or https://emby.my-domain.com, you should access to your media serveur.
Conclusion
With this method, you can make your container available both on your local network, with macvlan and on the web with traefik and a domain name.
I chose to use containers corresponding to media servers, but you can use this method for any type of container. The most important being how you set up your macvlan network and how you set up traefik.
Top comments (1)
Thanks for your contribution. Maybe something that is interesting too: If you want to add a static mac address to the macvlan container, you can do so with this configuration:
The
priority
directive is only available in docker-compose file version 2, sadly. This prioritizes the macvlan network, as the mac address is only assigned to one of the network interfaces. Without the priority the mac address might be assigned to the web nic. The default priority is0
.