Running Grocy in Podman

Posted on Apr 15, 2023

My SO actually asked me to setup something self-hosted! Wow. So today we are setting up grocy, and we are not using the easiest way to do it.

For some background we have given up trying to remember what we have in our chest freezer, so we’ll try Grocy for this purpose.

I’m using the project official containers, podman, macvlan and a pod in this case, and I ran into one minor issue in the process that I mean to document here. Spoiler: it’s not something wrong with Podman in this case, but my general setup.

TL;DR

If you just use the default networking setup (like Dockers, which is bridged with the main networking interface), don’t use the podman network create part, and remember to expose ports as needed below. Default port for the frontend is 8080.

# Only necessary if you have a complicated network setup!
podman network create \
  -d macvlan \
  -o parent=br-network \
  --subnet 192.0.2.0/24 \
  --gateway 192.0.2.1 \
  --ip-range 192.0.2.0/24 \
  examplenet

# Comment out the --network line if not using the above network!
podman pod create \
  --name grocy \
  --network examplenet \
  --ip 192.0.2.4 \
  --add-host "backend:127.0.0.1"

# Remove the --label line if you don't want auto updating containers!
podman container create \
  --pod grocy \
  --label io.containers.autoupdate=registry \
  --name grocy_frontend \
  --memory 256m \
  --cpus=1 \
  grocy/frontend:v3.3.2

# Update the environment variables below to suit your needs!
podman container create \
  --pod grocy \
  --label io.containers.autoupdate=registry \
  --name grocy_backend \
  --memory 512m \
  --cpus=1 \
  -v /app/grocy/data/:/var/www/data \
  -e GROCY_CULTURE=sv_SE \
  -e GROCY_MODE=production \
  -e GROCY_CURRENCY=SEK \
  grocy/backend:v3.3.2

# Not strictly necessary!
podman container create \
  --pod grocy \
  --label io.containers.autoupdate=registry \
  --name grocy_caddy \
  -v /app/grocy/caddy/config/:/config \
  -v /app/grocy/caddy/data/:/data \
  -v /app/grocy/caddy/Caddyfile:/etc/caddy/Caddyfile \
  --memory 256m \
  --cpus=1 \
  caddy:latest

podman generate systemd  --new --files --name grocy
systemctl daemon-reload

mkdir -p /app/grocy/data
mkdir -p /app/grocy/caddy/{config,data}

mkdir /app/grocy/data/viewcache
curl https://raw.githubusercontent.com/grocy/grocy/master/config-dist.php > /app/grocy/data/config.php
chown 82:82 /app/grocy/data/*

systemctl enable --now pod-grocy.service

Networking

First up, make sure we have a macvlan network to run this in. This is the same example as in my primer article on podman.

podman network create \
  -d macvlan \
  -o parent=br-network \
  --subnet 192.0.2.0/24 \
  --gateway 192.0.2.1 \
  --ip-range 192.0.2.0/24 \
  examplenet

If you are not using a stupid complicated setup like I do, skip this step. But remember to expose the necessary ports on the pod when creating it!

File system

I keep my container data in a dedicated file tree, per application. So I created a structure that looks something like this:

mkdir -p /app/grocy/data
mkdir -p /app/grocy/caddy/{config,data}

Pod creation

I thought more magic would go into creating a pod, but it’s really only another container wrapping a group of other containers, which share a bunch of namespaces. I like it.

podman pod create \
  --name grocy \
  --network examplenet \
  --ip 192.0.2.4 \
  --add-host "backend:127.0.0.1"

Here was the first minor thing I ran into when starting the pod – the front-end container expected to find it’s backend by hostname ‘backend’, but I named the backend container grocy_backend. So fixing this with the --add-host call at the end.

One quirk of using pods is that you expose ports on the pod, and not on the containers in the pod. If you need to change exposed ports, you need to recreate it. Since I use macvlan in my setup I don’t need to expose any ports.

Container creation

We will create three containers, one for the backend, one for the frontend and one for Caddy.

But why use Caddy when the frontend already runs nginx? I’m using an internal ACME server to manage my internal TLS, and Caddy is really easy to configure for this.

Frontend

This can probably run fine with even less allocated memory.

podman container create \
  --pod grocy \
  --label io.containers.autoupdate=registry \
  --name grocy_frontend \
  --memory 256m \
  --cpus=1 \
  grocy/frontend:v3.3.2

Nothing exciting here.

Backend

Nothing exciting here, except the environment variables. If you want to configure grocy more than below, this is done using environment variables as per the documentation.

As I’m not using the example docker-compose.yml to build and run this, we will need to manually perform a step otherwise done while building.

podman container create \
  --pod grocy \
  --label io.containers.autoupdate=registry \
  --name grocy_backend \
  --memory 512m \
  --cpus=1 \
  -v /app/grocy/data/:/var/www/data \
  -e GROCY_CULTURE=sv_SE \
  -e GROCY_MODE=production \
  -e GROCY_CURRENCY=SEK \
  grocy/backend:v3.3.2

The manual steps needed:

mkdir /app/grocy/data/viewcache
curl https://raw.githubusercontent.com/grocy/grocy/master/config-dist.php > /app/grocy/data/config.php
chown 82:82 /app/grocy/data/*

Some handy links:

Caddy

This container is optional. If you don’t want it, remember to expose port 8080 on the front-end container instead.

First up, create the container.

podman container create \
  --pod grocy \
  --label io.containers.autoupdate=registry \
  --name grocy_caddy \
  -v /app/grocy/caddy/config/:/config \
  -v /app/grocy/caddy/data/:/data \
  -v /app/grocy/caddy/Caddyfile:/etc/caddy/Caddyfile \
  --memory 256m \
  --cpus=1 \
  caddy:latest

Example /app/grocy/caddy/Caddyfile:

{
  email me@example.com
}

grocy.example.com
{
  reverse_proxy localhost:8080
}

My setup looks more like the zigbee2mqtt example in the Caddy primer but due to reasons I’m using a simple setup here. This setup will try to fetch a valid certificate from letsencrypt, so keep this in mind if you already run a reverse proxy or such.

systemd

This will split the pod systemd definition into different files, one per container.

podman generate systemd  --new --files --name grocy
systemctl daemon-reload

That should reload systemd and create the following files:

/etc/systemd/system/container-grocy_backend.service
/etc/systemd/system/container-grocy_caddy.service
/etc/systemd/system/container-grocy_frontend.service
/etc/systemd/system/pod-grocy.service

Finally, start the pod:

systemctl enable --now pod-grocy.service

Check if it’s up:

# podman pod ps
POD ID        NAME   STATUS   CREATED         INFRA ID      # OF CONTAINERS
1df2731a50f6  grocy  Running  46 minutes ago  0c1d6d9f698c  4

And all containers:

# podman ps --pod
[...]
0c1d6d9f698c  k8s.gcr.io/pause:3.5                                   48 minutes ago  Up 48 minutes ago  1df2731a50f6-infra  1df2731a50f6  grocy
74748ff2525d  docker.io/grocy/backend:v3.3.2   php-fpm81             47 minutes ago  Up 47 minutes ago  grocy_backend       1df2731a50f6  grocy
26cd5043ab44  docker.io/library/caddy:latest   caddy run --confi...  47 minutes ago  Up 47 minutes ago  grocy_caddy         1df2731a50f6  grocy
839d7dd91776  docker.io/grocy/frontend:v3.3.2  nginx -e /proc/se...  47 minutes ago  Up 47 minutes ago  grocy_frontend      1df2731a50f6  grocy

Backups

It’s enough to backup /app/grocy/data, which should contain database, configuration, and any uploaded media (like recipe images).

If you use Caddy like above, it might be worth backing up /app/grocy instead.

Conclusion

Now I have to go through that chest freezer to see what’s in it…