Set up home media server automatically with Ansible.
The playbooks in this media server can be used to set up:
- copyparty for sharing and uploading files.
- Jellyfin for viewing media.
- Calibre and calibre-web for uploading and reading books.
- A set of *arr apps I use, including Radarr, Sonarr, Prowlarr, SABnzbd, and Jellyseerr.
- Caddy for reverse proxying services so they're available on the web.
All deployed services using these playbooks are run in containers using Podman.
These steps will enable you to run the playbooks from your control node on the media server remote.
To install Ansible on your control node (i.e., the host where you are running the playbooks from), use uv.
After installing uv, pull this repository and run:
uv venv
source .venv/bin/activate
uv syncIf you're using VSCode, you'll need to set the Python interpeter Path.
- Press CTRL + Shift + P
- Enter Python: Select Interpreter
- Select .venv/bin/python
- Restart your terminal if you have one open. You should see
(ansible-media-server)at the start of your prompt if the virtual environment is activated correctly.
Prior to running any playbooks, double check that the required Ansible collections are installed:
ansible-galaxy collection install -r requirements.ymlPrior to running the playbooks here, ensure there is a user account called ansible on the target (i.e., the remote home server), and that it has the ability to run sudo without a password. Run these commands on the remote server (sudo access needed):
# Create user with a home directory
sudo useradd --create-home ansible
sudo groupadd ansible
sudo usermod -aG ansible ansible
# Set the user's password
sudo passwd ansible
# Give the user access to run sudo
sudo echo "ansible ALL = (root) NOPASSWD:ALL" > /etc/sudoers.d/ansibleAdditionally, ensure you add the public key of your control node to the ansible user's authorized_keys file.
First, make a public key if you don't have one on your control node.
ssh-keygen -t ed25519Then run the following command from your control node to add your public key to the ansible user's authorized keys. Substitute home-server with your home server's actual host name.
ssh ansible@home-server sh -c "cat - >> ~/.ssh/authorized_keys" < ~/.ssh/id_ed25119.pubYou should add your home server's IP address or host name to the homeserver host in the inventory/hosts.yml file.
To encrypt your own ansible_host, you can use:
ansible-vault encrypt_string 'remote-server' --name 'ansible_host'Substituting remote-server with your remote host's actual IP or host name. You can then paste that into the hosts.yml file.
You can test running commands on the remote host with:
ansible all -m command -a "uptime" -i inventoryIt's recommended to store API tokens and other secret variables in an Ansible vault. If you create an Ansible vault at group_vars/all/vault.yml, Ansible will pick them up automatically. You can store the password to the vault anywhere outside of this repository, but I store it in a file called ~/.ansible_vault_password.
You can edit the variables in the encrypted file with:
export ANSIBLE_VAULT_PASSWORD_FILE=~/.ansible_vault_password
export EDITOR=vim
ansible-vault edit group_vars/all/vault.ymlAll encrypted variables are given a v_ prefix. Deploying some services require certain secrets to be set, see the description for each service.
If you're using VSCode, install recommended extensions by searching for @recommended in the extensions search box and install all the extensions listed there.
This playbook should be run first to prep the server for later playbooks.
The main purpose of this playbook is to create a non-root user for running containers. It also runs other one-time setup tasks that other playbooks will depend on having been executed.
The podman user is used extensively to provide a non-root user for storing configuration files and for running containers as a non-root user.
This playbook should be run first on a new server. This is because the other playbooks depend on this user existing.
To create the podman user with user ID and group ID 1010, run:
ansible-playbook -i inventory setup-podman-user.ymlThis variable needs to be set before running this playbook:
| Name | Description | Requirement |
|---|---|---|
| v_podman_user_password | The hashed value of the podman user's password | Always required |
Copyparty is a file sharing server that supports a wide range of browsers and protocols.
Copyparty can be installed with the setup-copyparty.yml playbook. You can optionally turn on reverse proxying for copyparty using the copyparty_reverse_proxy variable. This creates a shared network with the caddy container so that Caddy can access copyparty.
To set up copyparty, run:
ansible-playbook -i inventory setup-copyparty.ymlYou can share parts of your server's file with copyparty by using the copyparty_volumes variables. This can be controlled by editing copyparty.volumes in the host_vars/homeserver.yml file.
volumes is a list of objects with three attributes:
web_path: The path on the server that the files will be accessible at. Use/to see your files when you first log in to copyparty.container_path: The path in the copyparty container that you want to mount your server's files at.host_path: The local path on your server that contains the files you want to share
This example creates two shares:
- The podman user's home directory, at
/ - The
/data/media/directory, at/media/
copyparty:
volumes:
- web_path: /
container_path: /podman/
host_path: /home/podman/
- web_path: /media/
container_path: /media/
host_path: /data/media/You can enable file sharing with the copyparty_enable_sharing variable. If this is set to true, you can define a path with copyparty_share_web_path where shares will be accessible from. The files you share don't get copied or moved, so there's no need to specify where on your host the files are shared from.
The following table outlines the secret variables that may need to be set for this playbook to work.
| Name | Description | Requirement |
|---|---|---|
| v_copyparty_admin_username | The admin username | Always required |
| v_copyparty_admin_password | A plaintext password used to log in as the admin | Always required |
Calibre is an open-source e-book manager. Calibre is a desktop GUI application, but can be accessed over the web using this setup. Calibre Web is a web optimized interface that enables you to access an existing Calibre library.
Images used:
Calibre and Calibre Web can be installed with the setup-calibre.yml playbook. You can optionally turn on reverse proxying for Calibre Web using the calibre_web_reverse_proxy variable. This creates a shared network with the caddy container so that Caddy can access Calibre Web. Note that, when reverse proxying Calibre, only the Calibre Web interface is exposed. The desktop GUI interface can only be accessed locally.
To set up Calibre, run:
ansible-playbook -i inventory setup-calibre.ymlAfter installing, ensure you follow these steps to set up your library:
- Calibre application setup. You don't necessarily need to enable Calibre's web server since we have calibre-web for that.
- Calibre Web application setup
NOTE: If you're reverse proxying Calibre Web, you might notice an issue with having to log in over and over again. To fix that, follow the steps in this comment. You need to set the security configuration to "Basic" by going to Admin > Edit Basic Configuration > Security Settings > Session Protection to fix this issue.
There are sane defaults set for Calibre so you shouldn't need to tweak them (see defaults here). One variable you might want to tweak is the location of the upload directory. This directory is where you can upload books so that they appear in the Calibre GUI file explorer, not where books are stored. That is controlled with the calibre_upload_dir variable.
Since Calibre is a GUI application, there are quirks with accessing it via a web interface. Here are some tips to use it:
- There are two way to upload files so that you can add new books. The first way is to copy them to your server at
/data/media/upload/books/(or wherever you setcalibre_upload_dir). These uploads can be accessed at the/books/directory when you click Add books in Calibre.- Tip: You might use Copyparty to upload files to this location.
- Alternately, use the Selkies Dashboard to upload files via a web uploader. To open the dashboard, click the light blue button on the left side of the screen and find the Files section. Files uploaded in this way get uploaded to
/config/Desktop. This path can be configured using theFILE_MANAGER_PATHenvironment variable.
- To paste from your clipboard into the web interface, you must use the Clipboard section in the Selkies Dashboard. The content you want to paste should be placed in the Server Clipboard text input.
- If you experience performance issues (i.e., low frame rate) in your browser, you can try one of two things. You can choose a hardware render node that support VAAPI with the
calibre_vaapi_render_node, or you can set the Encoder to jpeg in the Selkies Video settings. I've not been able to get the hardware encoding working, but the jpeg renderer is a lot quicker on my hardware (with worse image quality).
The following table outlines the secret variables that may need to be set for this playbook to work.
| Name | Description | Requirement |
|---|---|---|
| v_calibre_password | The password to access the desktoip GUI | Always required |
You might notice there is no setup for the calibre-web username and password. Those can only be configured through the application itself.
Jellyfin is an open source media server.
Jellyfin can be installed with the setup-jellyfin.yml playbook. You can optionally turn on reverse proxying for Jellyfin using the jellyfin_reverse_proxy variable. This creates a shared network with the caddy container so that Caddy can access Jellyfin.
To set up Jellyfin, run:
ansible-playbook -i inventory setup-jellyfin.ymlThere are sane defaults set for Jellyfin so you shouldn't need to tweak them (see defaults here). One variable you might want to tweak is the location of the media library on your localhost. That is controlled with the jellyfin_media_dir variable. But be aware that this directory must be within the the starrapps_media_dir if you are running the starrapps (see playbook below).
The following table outlines the secret variables that may need to be set for this playbook to work.
| Name | Description | Requirement |
|---|---|---|
| v_jellyfin_domain | The domain where your Jellyfin instance is served | When reverse proxying |
Caddy is used to reverse proxy the services on the media server so that they can be served over HTTPS on the web.
Caddy can be set up with the setup-caddy.yml playbook. The caddy role assumes you are using Cloudflare for your domains, so requires at minimum a Cloudflare API token to authenticate with.
You should run this playbook after setting up the services that will be reverse proxied. Otherwise, Caddy will complain about not being able to get certificates for your domains.
ansible-playbook -i inventory setup-caddy.ymlThe services that caddy reverse proxies should be defined in host_vars/homeserver.yml. The reverse_proxy_hosts should be a list of objects containing three attributes (see below). For example, this will set Caddy to reverse proxy two services:
caddy:
reverse_proxy_hosts:
- domain_name: the-public.domain.com
container_name: the_container_name
port: the_exposed_port
- domain_name: example.com
container_name: the_other_container_name
port: the_other_exposed_portAny container or pod that uses the web-services.network can be reverse proxied by Caddy. If you want to reverse proxy a service in a Pod, you should reference the name of the pod with the container_name, not the name of the service inside the pod.
If your host does not have a static IP (e.g., if your ISP does not allow it), there is an included script that is deployed that will update the IPv4 address of your domains to the host's IP address whenever it changes. To enable this feature, set:
caddy:
host_ip_is_static: falseAnd the script will be deployed automatically. All of the DNS records within the set caddy_cloudflare_zone_id (i.e., the given Zone in Cloudflare) will have the IP address of their DNS A records set to the remote host's IP address.
Note that you will need to enable port forwarding for ports 80/tcp, 443/tcp, and 443/udp on your router as well if you want your services publicly accessible.
The following table outlines the secret variables that may need to be set for this playbook to work.
| Name | Description | Requirement |
|---|---|---|
| v_caddy_cloudflare_api_token | The cloudflare API token used for authentication | Always required |
| v_caddy_cloudflare_zone_id | The zone ID of the DNS records pointing to the remote server | When host IP is not static |


