Setting up home assistant on a docker container

· ranguna's blog

# Installation

This will install Home Assistant on a docker container on a Linux machine. The steps below are based on my specific use case. This method of installation will not allow you to install Add-Ons to Home Assistant.

  1. Install your preferred Linux distro:
    1. The steps below will include some comments targeting the typical arch Linux minimal installation, you'll have to adapt them to your own chosen distro.
  2. Power loss recovery:
    1. Make sure your machine automatically powers on once power is available again;
      1. In my specific machine, there was an option to automatically power it on once AC power is detected;
    2. Make sure your machine connects to the internet on boot.
      1. I had to configure systemd-networkd and systemd-resolved by enabling both services and copying the contents of arch Linux live boot system's /etc/systemd/network to my installation's /etc/systemd/network;
  3. Install your preferred ssh server and make sure it is enabled on boot and accessible in the same network:
    pacman -Syu openssh
    systemctl enable --now sshd.service
    
  4. Install docker and docker-compose and make sure they are enabled on boot:
    pacman -Syu docker docker-compose
    systemctl enable --now docker.service
    
  5. Follow the official guide on how to install Home Assistant on a container. These were the steps I followed, but they might be outdated:
    mkdir -p $HOME/homeassistant/config && cd $HOME/homeassistant
    
    1. Run the Home Assistant container (replace MY_TIME_ZONE with your time zone from this list)):
      1docker run -d \
      2   --name homeassistant \
      3   --privileged \
      4   --restart=unless-stopped \
      5   -e TZ=MY_TIME_ZONE \
      6   -v $HOME/homeassistant/config:/config \
      7   --network=host \
      8   ghcr.io/home-assistant/home-assistant:stable
      
    2. Once Home Assistant is up and running, you can access it at http://localhost:8123/ from within the machine where the container is running or from any other machine in the same network by replacing localhost with the machine's IP address;
      1. If the links above don't work, try http://<machine's IP address or localhost>:8123/onboarding.html instead;
    3. Finish the onboarding process;
  6. ...
  7. Profit!

# Configuration Management

If you followed the installation section, Home Assistant configuration files should be located at $HOME/homeassistant/config with the following structure:

1$HOME/homeassistant/config
2├── configuration.yaml
3├── automations.yaml
4├── scripts.yaml
5├── secrets.yaml
6├── scenes.yaml

Where configuration.yaml is the main configuration file and the others get imported. I did not find a recommended way to organize the configuration files, so for my specific use case I decided to organize them by type, but you can organize them however you want:

 1$HOME/homeassistant/config
 2├── configuration.yaml
 3├── automations
 4│   ├── your_automation_1.yaml
 5│   ├── your_automation_2.yaml
 6│   └── ...
 7├── scripts
 8│   ├── your_script_1.yaml
 9│   ├── your_script_2.yaml
10│   └── ...
11├── secrets.yaml
12├── scenes
13│   ├── your_scene_1.yaml
14│   ├── your_scene_2.yaml
15│   └── ...
16└── ...

With the configuration files organized as above, the contents of configuration.yaml will look like this:

1automation: !include_dir_merge_list automations
2script: !include_dir_list scripts
3scene: !include_dir_list scenes

The above configuration change can be validated in Home Assistant's Developer Tools page, in the YAML tab, by clicking on Check configuration. You should be greeted with a message saying Configuration will not prevent Home Assistant from starting!, or something along those lines.

# Virtual Device Example

In my house I have a pallet stove that I control with a IR remote control. This stove takes 7W of power on idle, it takes a few seconds to turn on with the IR controller and takes a some minutes to shutdown completely. Since this stove is not a "smart device", I have two options to control it:

  1. Modify its internals , soI can merge it into my zigbee network and control it from there;
  2. Get an external IR controller to turn it on and off and a smart plug to shut it down when on idle.

Since I'm not the best with electronics and I didn't want to damage the stove's internals, I decided to go with option #2.

I got a Broadlink RM Mini 4 IR controller and a NOUS A1Z smart plug. Both of these devices are compatible with Home Assistant. To cater to the above flow of control, this is a possible configuration:

stateDiagram-v2
      [*] --> Off
      Off --> Idle : Stove consuming around 7W
      Idle --> TurningOn : IR controller turned on
      TurningOn --> On : IR controller turned on
      On --> TurningOff: IR controller turned off
      TurningOff --> Idle: Stove consuming around 7W
      Idle --> Off: Wall plug turned off

There are a few options to implement this flow of control, one of which is to create a virtual device comprised of both the IR controller and the smart plug. To achieve this, I put together the following yaml configuration:

Show YAML
  • Located in automations/stove.yaml:
  1- id: 'turn_on_stove_power'
  2  alias: Turn On stove Power
  3  description: ''
  4  trigger:
  5    - platform: state
  6      entity_id: [input_boolean.stove_desired_state]
  7      from: 'off'
  8      to: 'on'
  9  condition:
 10    - condition: device
 11      domain: switch
 12      device_id: 105196245911f2b1fb03248d11fdff38
 13      entity_id: switch.wall_plug_2_switch
 14      type: is_off
 15    - condition: state
 16      entity_id: input_select.stove_state
 17      state: Turned Off
 18  action:
 19    - type: turn_on
 20      domain: switch
 21      device_id: 105196245911f2b1fb03248d11fdff38
 22      entity_id: switch.wall_plug_2_switch
 23  mode: single
 24- id: 'set_stove_state_idle'
 25  alias: 'Set stove State Idle'
 26  description: ''
 27  mode: single
 28  trigger:
 29    - type: power
 30      platform: device
 31      device_id: 105196245911f2b1fb03248d11fdff38
 32      entity_id: sensor.wall_plug_2_active_power
 33      domain: sensor
 34      below: 10
 35      above: 5
 36      for:
 37        hours: 0
 38        minutes: 0
 39        seconds: 10
 40  condition:
 41    - condition: not
 42      conditions:
 43        - condition: state
 44          entity_id: input_select.stove_state
 45          state: Idle
 46  action:
 47    - service: input_select.select_option
 48      data:
 49        option: Idle
 50      target:
 51        entity_id: input_select.stove_state
 52- id: 'turn_on_stove'
 53  alias: Turn On stove
 54  description: ''
 55  trigger:
 56    - platform: state
 57      entity_id: [input_select.stove_state]
 58      from: 'Turned Off'
 59      to: Idle
 60      for:
 61        hours: 0
 62        minutes: 0
 63        seconds: 40
 64  condition:
 65    - condition: state
 66      entity_id: input_boolean.stove_desired_state
 67      state: "on"
 68    - condition: device
 69      type: is_on
 70      device_id: 0fc2386a6d618e8aaf8eeb7ed55dd955
 71      entity_id: remote.stove_controller
 72      domain: remote
 73  action:
 74    - service: remote.send_command
 75      target:
 76        entity_id: remote.stove_controller
 77      data:
 78        device: stove_remote
 79        command: power_on
 80        num_repeats: 3
 81        delay_secs: 3
 82        hold_secs: 2
 83  mode: single
 84- id: 'set_stove_state_turning_on'
 85  alias: 'Set stove State Turning On'
 86  description: ''
 87  mode: single
 88  trigger:
 89    - type: power
 90      platform: device
 91      device_id: 105196245911f2b1fb03248d11fdff38
 92      entity_id: sensor.wall_plug_2_active_power
 93      domain: sensor
 94      below: 270
 95      above: 190
 96      for:
 97        hours: 0
 98        minutes: 0
 99        seconds: 5
100  condition:
101    - condition: state
102      entity_id: input_select.stove_state
103      state: Idle
104  action:
105    - service: input_select.select_option
106      target:
107        entity_id: input_select.stove_state
108      data:
109        option: 'Turning On'
110- id: 'set_stove_state_turned_on'
111  alias: 'Set stove State Turned On'
112  description: ''
113  mode: single
114  trigger:
115    - type: power
116      platform: device
117      device_id: 105196245911f2b1fb03248d11fdff38
118      entity_id: sensor.wall_plug_2_active_power
119      domain: sensor
120      below: 70
121      above: 20
122      for:
123        hours: 0
124        minutes: 0
125        seconds: 15
126  condition:
127    - condition: state
128      entity_id: input_select.stove_state
129      state: 'Turning On'
130  action:
131    - service: input_select.select_option
132      target:
133        entity_id: input_select.stove_state
134      data:
135        option: 'Turned On'
136- id: 'turn_off_stove'
137  alias: Turn Off stove
138  description: ''
139  trigger:
140    - platform: state
141      entity_id: [input_boolean.stove_desired_state]
142      from: 'on'
143      to: 'off'
144  condition:
145    - condition: state
146      entity_id: input_select.stove_state
147      state: Turned On
148    - condition: device
149      type: is_on
150      device_id: 0fc2386a6d618e8aaf8eeb7ed55dd955
151      entity_id: remote.stove_controller
152      domain: remote
153  action:
154    - service: remote.send_command
155      data:
156        device: stove_remote
157        command: power_off
158        num_repeats: 3
159        delay_secs: 3
160        hold_secs: 2
161      target:
162        entity_id: remote.stove_controller
163  mode: single
164- id: 'set_stove_state_turning_off'
165  alias: 'Set stove State Turning Off'
166  description: ''
167  mode: single
168  trigger:
169    - type: power
170      platform: device
171      device_id: 105196245911f2b1fb03248d11fdff38
172      entity_id: sensor.wall_plug_2_active_power
173      domain: sensor
174      above: 48
175      below: 54
176      for:
177        hours: 0
178        minutes: 0
179        seconds: 20
180    - type: power
181      platform: device
182      device_id: 105196245911f2b1fb03248d11fdff38
183      entity_id: sensor.wall_plug_2_active_power
184      domain: sensor
185      above: 54
186      below: 58
187      for:
188        hours: 0
189        minutes: 0
190        seconds: 20
191    - type: power
192      platform: device
193      device_id: 105196245911f2b1fb03248d11fdff38
194      entity_id: sensor.wall_plug_2_active_power
195      domain: sensor
196      above: 58
197      below: 64
198      for:
199        hours: 0
200        minutes: 0
201        seconds: 20
202  condition:
203    - condition: state
204      entity_id: input_select.stove_state
205      state: 'Turned On'
206  action:
207    - service: input_select.select_option
208      target:
209        entity_id: input_select.stove_state
210      data:
211        option: 'Turning Off'
212- id: 'turn_off_stove_power'
213  alias: Turn Off stove Power
214  description: ''
215  trigger:
216    - platform: state
217      entity_id: [input_select.stove_state]
218      from: 'Turning Off'
219      to: Idle
220  condition:
221    - condition: device
222      device_id: 105196245911f2b1fb03248d11fdff38
223      entity_id: switch.wall_plug_2_switch
224      type: is_on
225      domain: switch
226  action:
227    - type: turn_off
228      domain: switch
229      device_id: 105196245911f2b1fb03248d11fdff38
230      entity_id: switch.wall_plug_2_switch
231  mode: single
232- id: 'set_stove_state_turned_off'
233  alias: 'Set stove State Turned Off'
234  description: ''
235  mode: single
236  trigger:
237    - type: power
238      platform: device
239      device_id: 105196245911f2b1fb03248d11fdff38
240      entity_id: sensor.wall_plug_2_active_power
241      domain: sensor
242      below: 1
243      above: -1
244      for:
245        hours: 0
246        minutes: 0
247        seconds: 10
248  condition:
249    - condition: not
250      conditions:
251        - condition: state
252          entity_id: input_select.stove_state
253          state: Turned Off
254  action:
255    - service: input_select.select_option
256      target:
257        entity_id: input_select.stove_state
258      data:
259        option: 'Turned Off'
260- description: 'set_stove_desired_state_on_on_external_operation'
261  alias: 'Set stove desired state on on external operation'
262  mode: single
263  trigger:
264    - platform: state
265      entity_id: [input_select.stove_state]
266      to: Turned On
267    - platform: state
268      entity_id: [input_select.stove_state]
269      to: Turning On
270  condition:
271    - condition: state
272      entity_id: input_boolean.stove_desired_state
273      state: "off"
274  action:
275    - service: input_boolean.turn_on
276      data: {}
277      target:
278        entity_id: input_boolean.stove_desired_state
279- description: 'set_stove_desired_state_off_on_external_operation'
280  alias: 'Set stove desired state off on external operation'
281  mode: single
282  trigger:
283    - platform: state
284      entity_id: [input_select.stove_state]
285      to: Turned Off
286    - platform: state
287      entity_id: [input_select.stove_state]
288      to: Turning Off
289  condition:
290    - condition: state
291      entity_id: input_boolean.stove_desired_state
292      state: "on"
293  action:
294    - service: input_boolean.turn_off
295      data: {}
296      target:
297        entity_id: input_boolean.stove_desired_state
298- description: 'notification_reminder_turn_on_stove'
299  alias: 'Reminder to turn on stove'
300  mode: single
301  trigger:
302    - platform: time
303      at: "17:00:00"
304  condition:
305    - condition: state
306      entity_id: input_select.stove_state
307      state: Turned Off
308    - type: is_temperature
309      condition: device
310      device_id: 0fc2386a6d618e8aaf8eeb7ed55dd955
311      entity_id: sensor.stove_controller_temperature
312      domain: sensor
313      below: 17
314  action:
315    - service: notify.mobile_app_YOU_MOBILE_NAME
316      data:
317        title: It's cold ❄️
318        message: Turn on stove ?
  • In configuration.yaml:
 1# your imports and other configurations above
 2
 3input_select:
 4stove_state:
 5  name: READ ONLY Current State of the stove
 6  options:
 7    - Idle
 8    - Turned Off
 9    - Turned On
10    - Turning Off
11    - Turning On
12  initial: Turned Off
13
14input_boolean:
15  stove_desired_state:
16    name: stove desired state
17    initial: off
18 
19switch:
20  - platform: template
21    switches:
22      stove_switch:
23        unique_id: stove_switch
24        friendly_name: stove Switch
25        value_template: "{{ is_state('input_boolean.stove_desired_state', 'on') }}"
26        turn_on:
27          service: input_boolean.turn_on
28          data_template:
29            entity_id: >
30              {% if is_state('input_select.stove_state', 'Turned Off') %}
31                input_boolean.stove_desired_state
32              {% else %}
33                switch.null
34              {% endif %}              
35        turn_off:
36          service: input_boolean.turn_off
37          data_template:
38            entity_id: >
39             {% if is_state('input_select.stove_state', 'Turned On') or is_state('input_select. stove_state', 'Turned Off') %}
40                input_boolean.stove_desired_state
41              {% else %}
42                switch.null
43              {% endif %}             
44
45# your other configuration below

This also includes a notification to your phone when the temperature is low.

# Accessing HA outside your local network

# Using cloudflared

  1. Setup a cloudflare account with a custom domain.
  2. Install cloudflared:
    pacman -Syu cloudflared
    
  3. Login to cloudflare:
    cloudflared login
    
  4. Setup a tunnel pointing to your home assistant instance with your custom domain:
    cloudflared tunnel --hostname <your sub domain>.<your domain> --url localhost:8123 --name <your tunnel preferred name>
    
  5. Allowlist your local ip to connect to homeassistant by adding the following to the root of your $HOME/homeassistant/config/configuration.yaml file:
    1http:
    2   use_x_forwarded_for: true
    3   trusted_proxies:
    4      - ::1
    
  6. Access <your sub domain>.<your domain>;
  7. ...
  8. Profit!

Create a systemd service to run cloudflared on boot:

  1. If you have a cloudflared process running, kill it:
    killall cloudflared
    
  2. Create a unit file at /etc/systemd/system/cloudflared.service with the following content:
    [Unit]
    Description=cloudflared-homeassistant
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=<your user>
    Group=<your group, usually the same name as your user>
    ExecStart=cloudflared tunnel --hostname <your sub domain>.<your domain> --url localhost:8123 --name <your tunnel preferred name>
    Restart=on-failure
    RestartSec=10
    
    [Install]
    WantedBy=multi-user.target
    
  3. Enable and start the service:
    systemctl enable --now cloudflared.service
    

Setting up cloudflare VPN-like access:

Keep in mind that you will not be able to login with the home assistant app if you enabled this.

  1. Go to your cloudflare dashboard;
  2. Go to Access;
  3. Launch Zero Trust;
  4. Go to Access > Applications > Add an Application > Self-Host;
  5. Fill out the form as you prefer, but make sure to set the Application domain to <your sub domain>.<your domain>;
  6. Carry on with remaining steps, leave everything as default if you don't know what the configuration options mean;
  7. Access <your sub domain>.<your domain>;
  8. ...
  9. Profit! Keep in mind that enabling the above will make you unable to use home assistant app, you'll have to use the web interface instead.

Using the app:

  1. Disable cloudflare VPN-like access if you have it enabled;
  2. Edit your $HOME/homeassistant/config/configuration.yaml file and add the following:
    1homeassistant:
    2   external_url: https://<your sub domain>.<your domain>/
    
  3. Restart home assistant;
  4. Install the Home Assistant app on your phone;
  5. Add your home assistant instance with https://<your sub domain>.<your domain>/ as the URL;
    1. If it fails, try clearing the app's cache and data, uninstalling the app, reboot your phone and try again from the step 1;
  6. ...
  7. Profit!