Skip to content

Commit 45d7775

Browse files
committed
podman quadlet systemd
Signed-off-by: David Kirwan <davidkirwanirl@gmail.com>
1 parent acb5989 commit 45d7775

5 files changed

Lines changed: 189 additions & 11 deletions

File tree

Makefile

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ SHELL := /bin/bash
88
APP_HOST ?= 0.0.0.0
99
APP_PORT ?= 8080
1010
CONTAINERFILE ?= Containerfile
11-
IMAGE ?= asset-monitoring:latest
11+
IMAGE ?= quay.io/dkirwan/asset-monitoring:latest
12+
BUILD_IMAGE ?= quay.io/dkirwan/asset-monitoring:dev
13+
ifneq ($(filter true 1 yes,$(USE_DEV_IMAGE)),)
14+
RUN_IMAGE := $(BUILD_IMAGE)
15+
else
16+
RUN_IMAGE := $(IMAGE)
17+
endif
1218
DATA_DIR ?= $(CURDIR)/data
1319
CONTAINER_DATA_DIR ?= /data
1420
PRICE_HISTORY_DB_PATH ?= $(CONTAINER_DATA_DIR)/asset_history.db
@@ -66,8 +72,8 @@ brakeman: ## Run Brakeman
6672

6773
security: audit brakeman ## Run security checks (bundle-audit + brakeman)
6874

69-
podman-build: ## Build the container image tagged asset-monitoring:latest
70-
podman build -t $(IMAGE) -f $(CONTAINERFILE) .
75+
podman-build: ## Build the container image as quay.io/dkirwan/asset-monitoring:dev
76+
podman build -t $(BUILD_IMAGE) -f $(CONTAINERFILE) .
7177

7278
podman-run: ## Run the container (SQLite in ./data bind-mounted to /data)
7379
@mkdir -p $(DATA_DIR)
@@ -76,7 +82,7 @@ podman-run: ## Run the container (SQLite in ./data bind-mounted to /data)
7682
-e PRICE_HISTORY_DB_PATH=$(PRICE_HISTORY_DB_PATH) \
7783
-e PORTFOLIO_DB_PATH=$(PORTFOLIO_DB_PATH) \
7884
-v $(DATA_DIR):$(CONTAINER_DATA_DIR):Z,U \
79-
$(IMAGE)
85+
$(RUN_IMAGE)
8086

8187
podman_build: podman-build ## Alias for podman-build
8288

README.asciidoc

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,139 @@ Open http://localhost:8080. `rake dev` adds auto-reload. `rake help` lists all t
1818

1919
== Run with Podman
2020

21+
Images are published to link:https://quay.io/dkirwan/asset-monitoring[quay.io/dkirwan/asset-monitoring].
22+
23+
* `make podman-build` / `rake podman:build` tags a local build as `quay.io/dkirwan/asset-monitoring:dev`
24+
* `make podman-run` / `rake podman:run` runs `quay.io/dkirwan/asset-monitoring:latest` (pulled from Quay if not present locally)
25+
26+
Run the published image:
27+
28+
```
29+
make podman-run
30+
```
31+
32+
Run a local dev build:
33+
2134
```
22-
rake podman:build
23-
rake podman:run
35+
make podman-build
36+
USE_DEV_IMAGE=true make podman-run
2437
```
2538

2639
Binds `./data` → `/data` with SQLite for price and portfolio history (365-day retention by default). Override with `DATA_DIR`, `PRICE_HISTORY_RETENTION_DAYS`, `PRICE_HISTORY_DB_PATH`, `PORTFOLIO_DB_PATH`.
2740

41+
Override image tags:
42+
43+
```
44+
BUILD_IMAGE=quay.io/dkirwan/asset-monitoring:dev make podman-build
45+
IMAGE=quay.io/dkirwan/asset-monitoring:latest make podman-run
46+
```
47+
48+
After building locally, push the dev image to Quay when you are ready to publish:
49+
50+
```
51+
podman push quay.io/dkirwan/asset-monitoring:dev
52+
```
53+
54+
== Running as a systemd service
55+
56+
Podman integrates with systemd through link:https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html[Quadlet].
57+
A `.container` unit in `deploy/asset-monitoring.container` describes the service; systemd generates `asset-monitoring.service` from it at `daemon-reload` time.
58+
59+
This matches `rake podman:run` / `make podman-run`: the Quay `:latest` image, port `8080`, SQLite under `/data`, and 365-day price history retention.
60+
61+
Quadlet-generated units are *transient* — do not run `systemctl enable` on them (that fails with "Unit is transient or generated"). Boot wiring is handled by the `[Install]` section in the `.container` file when you run `daemon-reload`.
62+
63+
=== Requirements
64+
65+
* Podman with Quadlet support (Fedora 38+, RHEL 9+, or recent Podman elsewhere)
66+
* systemd user session (for a rootless service) or root (for a system service)
67+
68+
=== Install (rootless, per user)
69+
70+
Create a data directory, pull the image, and install the Quadlet unit:
71+
72+
[source,bash]
73+
----
74+
mkdir -p ~/.local/share/asset-monitoring/data
75+
podman pull quay.io/dkirwan/asset-monitoring:latest
76+
mkdir -p ~/.config/containers/systemd
77+
cp deploy/asset-monitoring.container ~/.config/containers/systemd/
78+
systemctl --user daemon-reload
79+
systemctl --user start asset-monitoring.service
80+
----
81+
82+
`daemon-reload` reads the `.container` file, generates the service unit, and applies the `[Install]` section (`WantedBy=default.target`) so the service starts on future user logins. Use `start` (not `enable`) to run it immediately.
83+
84+
Open http://localhost:8080/portfolio once the service is active.
85+
86+
The default unit stores SQLite data in `~/.local/share/asset-monitoring/data`. Edit the `Volume=` line in `~/.config/containers/systemd/asset-monitoring.container` before `daemon-reload` if you want a different path.
87+
88+
To keep the service running after you log out (and across reboots without a graphical login), enable linger:
89+
90+
[source,bash]
91+
----
92+
loginctl enable-linger "$USER"
93+
----
94+
95+
=== Install (system-wide)
96+
97+
For a machine-wide service running as root:
98+
99+
[source,bash]
100+
----
101+
sudo mkdir -p /var/lib/asset-monitoring/data
102+
sudo podman pull quay.io/dkirwan/asset-monitoring:latest
103+
----
104+
105+
Copy `deploy/asset-monitoring.container` to `/etc/containers/systemd/asset-monitoring.container`, then adjust:
106+
107+
* `Volume=` — e.g. `/var/lib/asset-monitoring/data:/data:Z` (replace the default `%h/.local/share/...` path)
108+
* `[Install]` — set `WantedBy=multi-user.target`
109+
110+
Reload and start:
111+
112+
[source,bash]
113+
----
114+
sudo systemctl daemon-reload
115+
sudo systemctl start asset-monitoring.service
116+
----
117+
118+
=== Interacting with the service
119+
120+
Rootless (per-user) commands:
121+
122+
[source,bash]
123+
----
124+
systemctl --user status asset-monitoring.service
125+
systemctl --user restart asset-monitoring.service
126+
systemctl --user stop asset-monitoring.service
127+
journalctl --user -u asset-monitoring.service -f
128+
----
129+
130+
System-wide commands use `sudo systemctl` and `sudo journalctl` without `--user`.
131+
132+
After editing the `.container` file, reload systemd before restarting:
133+
134+
[source,bash]
135+
----
136+
systemctl --user daemon-reload
137+
systemctl --user restart asset-monitoring.service
138+
----
139+
140+
=== Updating the image
141+
142+
Pull a newer image and restart:
143+
144+
[source,bash]
145+
----
146+
podman pull quay.io/dkirwan/asset-monitoring:latest
147+
systemctl --user restart asset-monitoring.service
148+
----
149+
150+
The unit sets `io.containers.autoupdate=registry` so link:https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html[`podman auto-update`] can refresh the image on a schedule if you enable that separately.
151+
152+
To run a local `:dev` image instead, change `Image=` in the `.container` file to `quay.io/dkirwan/asset-monitoring:dev` (or your own tag), run `daemon-reload`, then restart the service.
153+
28154
== API
29155

30156
| Path | Purpose
@@ -50,6 +176,12 @@ Portfolio snapshots are recorded on save and after each successful scrape. Stock
50176
|`RACK_ENV` |`production` / `development` / `test`
51177
|===
52178

179+
Make/Rake-only variables (not read from `.env`):
180+
181+
* `USE_DEV_IMAGE` — when `true`, `1`, or `yes`, `podman:run` uses `quay.io/dkirwan/asset-monitoring:dev` instead of `:latest`
182+
* `BUILD_IMAGE` — image tag for `podman:build` (default: `quay.io/dkirwan/asset-monitoring:dev`)
183+
* `IMAGE` — image tag for `podman:run` when `USE_DEV_IMAGE` is unset (default: `quay.io/dkirwan/asset-monitoring:latest`)
184+
53185
== Kubernetes
54186

55187
Example manifests in `kubernetes/`. When using SQLite persistence:

Rakefile

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,24 @@ ensure_gem_exec_path!
2424
APP_HOST = ENV.fetch('HOST', '0.0.0.0')
2525
APP_PORT = ENV.fetch('PORT', '8080')
2626
CONTAINERFILE = 'Containerfile'
27-
IMAGE = 'asset-monitoring:latest'
27+
DEFAULT_IMAGE = 'quay.io/dkirwan/asset-monitoring:latest'
28+
DEFAULT_BUILD_IMAGE = 'quay.io/dkirwan/asset-monitoring:dev'
2829
DATA_DIR = File.expand_path(ENV.fetch('DATA_DIR', 'data'), __dir__)
2930
CONTAINER_DATA_DIR = '/data'
3031
PRICE_HISTORY_DB_PATH = File.join(CONTAINER_DATA_DIR, 'asset_history.db')
3132
RACKUP_CMD = ['bundle', 'exec', 'rackup', 'config.ru', '--host', APP_HOST, '-p', APP_PORT].freeze
3233

34+
def build_image
35+
ENV.fetch('BUILD_IMAGE', DEFAULT_BUILD_IMAGE)
36+
end
37+
38+
def run_image
39+
use_dev = ENV.fetch('USE_DEV_IMAGE', '').strip.downcase
40+
return build_image if %w[true 1 yes].include?(use_dev)
41+
42+
ENV.fetch('IMAGE', DEFAULT_IMAGE)
43+
end
44+
3345
RSpec::Core::RakeTask.new(:spec)
3446
RuboCop::RakeTask.new(:rubocop)
3547

@@ -94,7 +106,7 @@ end
94106
namespace :podman do
95107
desc 'Build the container image'
96108
task :build do
97-
sh 'podman', 'build', '-t', IMAGE, '-f', CONTAINERFILE, '.'
109+
sh 'podman', 'build', '-t', build_image, '-f', CONTAINERFILE, '.'
98110
end
99111

100112
desc 'Run the container (1-year price history, SQLite in ./data bind-mounted to /data)'
@@ -112,7 +124,7 @@ namespace :podman do
112124
'-e', "PRICE_HISTORY_DB_PATH=#{db_path}",
113125
'-e', "PORTFOLIO_DB_PATH=#{portfolio_db_path}",
114126
'-v', "#{DATA_DIR}:#{container_data_dir}:Z,U",
115-
IMAGE
127+
run_image
116128
end
117129
end
118130

deploy/asset-monitoring.container

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[Unit]
2+
Description=asset-monitoring metrics exporter and portfolio tracker
3+
Documentation=https://github.com/davidkirwan/asset_monitoring
4+
After=network-online.target
5+
Wants=network-online.target
6+
7+
[Container]
8+
Image=quay.io/dkirwan/asset-monitoring:latest
9+
ContainerName=asset-monitoring
10+
PublishPort=8080:8080
11+
Volume=%h/.local/share/asset-monitoring/data:/data:Z
12+
Environment=PRICE_HISTORY_RETENTION_DAYS=365
13+
Environment=PRICE_HISTORY_DB_PATH=/data/asset_history.db
14+
Environment=PORTFOLIO_DB_PATH=/data/portfolio.db
15+
Environment=RACK_ENV=production
16+
Label=io.containers.autoupdate=registry
17+
18+
[Service]
19+
Restart=always
20+
TimeoutStartSec=300
21+
22+
[Install]
23+
WantedBy=default.target

tasks/help.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ Security:
2323
rake brakeman / make brakeman - Run brakeman only
2424

2525
Podman:
26-
rake podman:build / make podman-build - Build the container image
27-
rake podman:run / make podman-run - Run the container (365-day retention, SQLite in ./data)
26+
rake podman:build / make podman-build - Build image as quay.io/dkirwan/asset-monitoring:dev
27+
rake podman:run / make podman-run - Run quay.io/dkirwan/asset-monitoring:latest (365-day retention, SQLite in ./data)
28+
29+
Image tags (override via env):
30+
BUILD_IMAGE=quay.io/dkirwan/asset-monitoring:dev - Tag for podman:build
31+
IMAGE=quay.io/dkirwan/asset-monitoring:latest - Tag for podman:run
32+
USE_DEV_IMAGE=true - Run the :dev image instead of :latest
2833

2934
podman:run defaults (override via env):
3035
PRICE_HISTORY_RETENTION_DAYS=365 - Retain price history for one year

0 commit comments

Comments
 (0)