Skip to content

Commit 2b2ce2a

Browse files
authored
Merge pull request #3 from DtxdF/appjail-quick-start
Add AppJail quick-start guide
2 parents a42d32b + ed2fbfe commit 2b2ce2a

1 file changed

Lines changed: 318 additions & 11 deletions

File tree

docs/guides/quick-start.md

Lines changed: 318 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ Get daemonless containers running on FreeBSD in 5 minutes.
2626
!!! tip "Customize Your Guide"
2727
Scroll to [Interactive Configuration](#interactive-configuration) at the bottom to set your PUID, PGID, and paths. All commands will update automatically.
2828

29-
## Prerequisites
29+
## Podman
30+
31+
### Prerequisites
3032

3133
!!! failure "Root Privileges Required"
3234
**Podman on FreeBSD currently requires root.** Rootless mode is not yet supported. All commands in this guide must be run as root (or via `sudo`/`doas`).
@@ -41,9 +43,9 @@ pkg install podman-suite
4143
Currently, a temporary patch for `ocijail` is required for .NET applications (Radarr/Sonarr).
4244
See [ocijail patch](ocijail-patch.md).
4345

44-
## Host Configuration
46+
### Host Configuration
4547

46-
### 1. Enable Networking
48+
#### 1. Enable Networking
4749
Configure the kernel to allow packet filtering for local traffic and ensure `fdescfs` is mounted.
4850

4951
```bash
@@ -56,7 +58,7 @@ mount -t fdescfs fdesc /dev/fd
5658
echo 'fdesc /dev/fd fdescfs rw 0 0' >> /etc/fstab
5759
```
5860

59-
### 2. Configure Firewall (`pf.conf`)
61+
#### 2. Configure Firewall (`pf.conf`)
6062
Add the following to `/etc/pf.conf`. Replace `@INTERFACE@` if your external interface is different.
6163

6264
```
@@ -76,14 +78,14 @@ Reload the configuration:
7678
pfctl -f /etc/pf.conf
7779
```
7880

79-
### 3. Start Podman
81+
#### 3. Start Podman
8082

8183
```bash
8284
sysrc podman_enable=YES
8385
service podman start
8486
```
8587

86-
## Run Your First Container
88+
### Run Your First Container
8789

8890
We'll start with **Tautulli**, a lightweight Python app that doesn't require special permissions.
8991

@@ -102,7 +104,7 @@ podman logs -f tautulli
102104
```
103105
Access the UI at: `http://localhost:8181`
104106

105-
## .NET Applications
107+
### .NET Applications
106108
Applications like **Radarr** and **Sonarr** require the `allow.mlock` jail annotation to function correctly on FreeBSD.
107109

108110
```bash
@@ -114,7 +116,7 @@ podman run -d --name radarr \
114116
@REGISTRY@/radarr:latest
115117
```
116118

117-
## Advanced Setup (Optional)
119+
### Advanced Setup (Optional)
118120

119121
=== "ZFS Storage"
120122
If you're using ZFS, configure Podman to use it for proper copy-on-write layering and snapshot support:
@@ -135,13 +137,318 @@ podman run -d --name radarr \
135137
```
136138
See [Networking Guide](networking.md) for details.
137139

140+
## AppJail
141+
142+
### Prerequisites
143+
144+
!!! warning
145+
This quick start guide is intended exclusively for OCI container users. For more general information, see the [AppJail Handbook](https://appjail.readthedocs.io), `appjail-tutorial(7)`, `director(1)` and `director-spec(5)` man pages.
146+
147+
!!! warning
148+
If you plan to use ZFS, set it up before running AppJail. See below for details.
149+
150+
Install AppJail and Director
151+
152+
```bash
153+
pkg install -y appjail sysutils/py-director
154+
```
155+
156+
### Host Configuration
157+
158+
#### 0. Configure trusted users (optional)
159+
160+
AppJail requires privileges to run, but it can be integrated with tools such as [security/doas](https://freshports.org/security/doas) to run it as a user without root privileges. This is recommended when you are the only person using the computer and have privileges, or in cases where there are more than two sysadmins or developers on the same server with root access.
161+
162+
**/usr/local/etc/doas.conf**:
163+
164+
```
165+
permit nopass keepenv :appjail as root cmd appjail
166+
```
167+
168+
This rule also assumes that you have a group named `appjail`. If you don't, don't worry:
169+
170+
```bash
171+
pw groupadd -n appjail
172+
```
173+
174+
To add your user to the `appjail` group simply run the following:
175+
176+
```bash
177+
pw groupmod -n appjail -m "$USER"
178+
```
179+
180+
Where `$USER` is your user. For these changes to take effect, you must log back into the system if you are adding yourself.
181+
182+
Similarly, there is a variant for `appjail-config` named `appjail-config-user`. The instructions for using it are similar to the above:
183+
184+
```
185+
permit nopass :appjail as root cmd appjail-config
186+
```
187+
188+
To test the changes above, simply run the following as a non-root user:
189+
190+
```bash
191+
appjail help
192+
appjail-config-user help
193+
```
194+
195+
See also: [Trusted Users on AppJail Handbook](https://appjail.readthedocs.io/en/latest/trusted-users/).
196+
197+
#### 1. Enable Networking
198+
199+
!!! tip
200+
Not all network options require packet filtering (for example: aliasing, bridge-only, vnet-only, etc.), but it is particularly useful for Virtual Networks, a common network option in deployments.
201+
202+
AppJail does not require any configuration, as it uses the default settings, but we recommend that you at least configure the `EXT_IF` parameter to point to your external interface.
203+
204+
**/usr/local/etc/appjail/appjail.conf**:
205+
206+
```
207+
EXT_IF=@INTERFACE@
208+
```
209+
210+
NAT and port forwarding require IP forwarding, so let's set it up:
211+
212+
```bash
213+
sysrc gateway_enable="YES"
214+
sysctl net.inet.ip.forwarding=1
215+
```
216+
217+
#### 2. Configure Firewall (pf.conf)
218+
219+
AppJail uses anchors like other applications that use `pf(4)` as a backend. Just enable `pf(4)` in your `rc.conf(5)`, put the anchors in the `pf.conf(5)` file and reload the rules.
220+
221+
```bash
222+
# Enable pf(4):
223+
sysrc pf_enable="YES"
224+
sysrc pflog_enable="YES"
225+
# Put the anchors in pf.conf(5):
226+
cat << "EOF" >> /etc/pf.conf
227+
nat-anchor "appjail-nat/jail/*"
228+
nat-anchor "appjail-nat/network/*"
229+
rdr-anchor "appjail-rdr/*"
230+
EOF
231+
# Reload the pf(4) rules:
232+
service pf reload
233+
# Or restart the rc(8) script if you don't have pf(4) started:
234+
service pf restart
235+
service pflog restart
236+
```
237+
238+
See also: [Packet Filter on AppJail Handbook](https://appjail.readthedocs.io/en/latest/networking/packet-filter/).
239+
240+
#### 3. Start AppJail
241+
242+
If you want to start your jails at startup, enable AppJail's `rc(8)` script:
243+
244+
```
245+
sysrc appjail_enable=YES
246+
```
247+
248+
### .NET Applications
249+
250+
In an `appjail-template(5)` file, you can define any `jail(8)` parameter, including the one used by .NET applications.
251+
252+
### Run Your First Container
253+
254+
Let's deploy a simple web application.
255+
256+
```console
257+
# mkdir -p -- "@CONTAINER_CONFIG_ROOT@/tautulli"
258+
# appjail oci run \
259+
-d \
260+
-u root \
261+
-o overwrite=force \
262+
-o virtualnet=":<random> default" \
263+
-o nat \
264+
-o expose="8181:8181" \
265+
-o container="args:--pull" \
266+
-o ephemeral \
267+
-o fstab="@CONTAINER_CONFIG_ROOT@/tautulli /config" \
268+
-e PUID=@PUID@ \
269+
-e PGID=@PGID@ \
270+
@REGISTRY@/tautulli:latest tautulli
271+
...
272+
[00:00:54] [ info ] [tautulli] Detached: pid:97368, log:jails/tautulli/container/2026-03-22.log
273+
# appjail jail list -j tautulli
274+
STATUS NAME ALT_NAME TYPE VERSION PORTS NETWORK_IP4
275+
UP tautulli - thick 15.0-RELEASE - 10.0.0.3
276+
# appjail jail list -j tautulli name container_pid
277+
NAME CONTAINER_PID
278+
tautulli 97368
279+
# appjail logs tail jails/tautulli/container/2026-03-22.log -f
280+
2026-03-22 04:49:07 - ERROR :: MainThread : Tautulli PlexTV :: PlexTV called, but no token provided.
281+
2026-03-22 04:49:07 - ERROR :: MainThread : Tautulli PlexTV :: PlexTV called, but no token provided.
282+
2026-03-22 04:49:08 - INFO :: MainThread : Tautulli WebStart :: Initializing Tautulli web server...
283+
2026-03-22 04:49:08 - WARNING :: MainThread : Tautulli WebStart :: Web server authentication is disabled!
284+
2026-03-22 04:49:08 - INFO :: MainThread : Tautulli WebStart :: Thread Pool Size: 10.
285+
2026-03-22 04:49:08 - INFO :: MainThread : Tautulli WebStart :: Starting Tautulli web server on http://0.0.0.0:8181/
286+
/app/tautulli/lib/cherrypy/process/servers.py:410: UserWarning: Unable to verify that the server is bound on 8181
287+
warnings.warn(msg)
288+
2026-03-22 04:49:13 - INFO :: MainThread : [22/Mar/2026:04:49:13] ENGINE Serving on http://0.0.0.0:8181
289+
2026-03-22 04:49:18 - INFO :: MainThread : Tautulli is ready!
290+
```
291+
292+
Access the UI at `http://10.0.0.3:8181`
293+
294+
**Notes**:
295+
296+
1. `-d`: The process will run in the background.
297+
2. `-u root`: We run `s6` as root, but keep in mind that the process is already running as the `bsd` user inside the jail, which is also mapped based on the `PUID` and `PGID` environment variables. We recommend specifying the user explicitly. See [this pr](https://github.com/daemonless/dbuild/pull/7) for more details.
298+
3. `-o overwrite=force`: Destroy the jail if it already exists, so that AppJail will recreate it instead of refusing to do so.
299+
4. `-o virtualnet=":<random> default" -o nat -o expose="8181:8181"`: Network options. In this case, we chose to use Virtual Networks.
300+
5. `-o container="args:--pull"`: Let's pull the image every time `buildah(1)` detects changes, so that AppJail always runs the jail using the latest image.
301+
6. `-o ephemeral`: Mark this jail as ephemeral, so that when it stops (or starts, in the event of a power outage on the computer), AppJail will destroy it.
302+
7. `@REGISTRY@/tautulli:latest tautulli`: The image, tag, and the jail name. The tag is optional.
303+
304+
### AppJail Director
305+
306+
Although you can use AppJail exclusively to deploy containers, it is recommended that you use AppJail Director. Environment variables set by `appjail-oci(1)` `run` will not be preserved after restarting the jail. You can use various `appjail-oci(1)` subcommands, such as `set-user`, `set-env`, etc., and then run the `from` subcommand, but this does not scale well when there are multiple containers. Another advantage is that Director defines its deployment file in YAML format declaratively, so it can be easily shared.
307+
308+
For example, the above deployment can easily be translated into a Director file:
309+
310+
**appjail-director.yml**:
311+
312+
```yaml
313+
options:
314+
- virtualnet: ':<random> default'
315+
- nat:
316+
services:
317+
tautulli:
318+
name: tautulli
319+
options:
320+
- expose: '8181:8181'
321+
- container: 'boot args:--pull'
322+
oci:
323+
user: root
324+
environment:
325+
- PUID: @PUID@
326+
- PGID: @PGID@
327+
volumes:
328+
- config: /config
329+
volumes:
330+
config:
331+
device: '@CONTAINER_CONFIG_ROOT@/tautulli'
332+
```
333+
334+
**Makejail**:
335+
336+
```
337+
ARG tag=latest
338+
339+
OPTION overwrite=force
340+
OPTION from=@REGISTRY@/tautulli:${tag}
341+
```
342+
343+
**.env**:
344+
345+
```
346+
DIRECTOR_PROJECT=tautulli
347+
```
348+
349+
By default, `director(1)` uses `Makejail` (which is assumed to be in the same directory as the Director file) as its `appjail-makejail(5)` and executes it. Some options are defined in a `appjail-makejail(5)` file, while others are defined per service. The convention is to specify options that do not change frequently in a `appjail-makejail(5)` file and the rest per service in the Director file. You can also set parameters using `ARG`, as we did above to specify the image tag, whose default value is `latest`. Finally, we define a `.env` file with environment variables loaded by Director.
350+
351+
**Console**:
352+
353+
```
354+
# appjail-director up
355+
Starting Director (project:tautulli) ...
356+
Creating tautulli (tautulli) ... Done.
357+
- Configuring the user (OCI) ... Done.
358+
- Configuring the environment (OCI):
359+
- PUID ... Done.
360+
- PGID ... Done.
361+
Starting tautulli (tautulli) ... Done.
362+
Finished: tautulli
363+
# appjail-director info
364+
tautulli:
365+
state: DONE
366+
last log: /root/.director/logs/2026-03-22_19h02m04s
367+
locked: false
368+
services:
369+
+ tautulli (tautulli)
370+
# ls /root/.director/logs/2026-03-22_19h02m04s/tautulli/
371+
makejail.log oci-environment.log oci-user.log start.log
372+
# tail -1 /root/.director/logs/2026-03-22_19h02m04s/tautulli/start.log
373+
[00:00:05] [ info ] [tautulli] Detached: pid:83091, log:jails/tautulli/container/2026-03-22.log
374+
```
375+
376+
### Advanced Setup (Optional)
377+
378+
=== "ZFS Storage"
379+
To enable ZFS, simply add `ENABLE_ZFS=1` to your `appjail.conf(5)` file. You may also need to configure `ZPOOL`, `ZROOTFS`, and `ZOPTS` if the default values do not suit your environment.
380+
381+
See also: [ZFS on AppJail Handbook](https://appjail.readthedocs.io/en/latest/ZFS/).
382+
383+
=== "Container DNS"
384+
AppJail copies `DEFAULT_RESOLV_CONF` to the jail's `resolv.conf(5)` file, whose default value is `/etc/resolv.conf`. Since this file can be modified by many programs, it is recommended that you configure a custom `resolv.conf(5)` file in a more stable location, such as `/usr/local/etc/appjail/resolv.conf`.
385+
386+
**/usr/local/etc/appjail/appjail.conf**:
387+
388+
```
389+
DEFAULT_RESOLV_CONF=/usr/local/etc/appjail/resolv.conf
390+
```
391+
392+
AppJail can be integrated with a third-party DNS server, and we can configure that server to read a `hosts(5)` file modified by `appjail-dns(8)`. Our first-class citizen is `dns/dnsmasq`, so let’s set it up. Before doing so, keep in mind that our `resolv.conf(5)` must point to an IP address where DNSMasq can receive packets, but the problem is that if we use a dynamic IP address, this can be problematic. To create a more deterministic environment, we’ll create an `if_tap(4)` interface and set a static IP address, so that our jails point to DNSMasq using that IP address.
393+
394+
```bash
395+
sysrc cloned_interfaces="tap0"
396+
sysrc ifconfig_tap0_name="ajdns"
397+
sysrc ifconfig_ajdns="inet 172.0.0.1/32"
398+
service netif cloneup
399+
service netif start ajdns
400+
```
401+
402+
**/usr/local/etc/appjail/resolv.conf**:
403+
404+
```
405+
nameserver 172.0.0.1
406+
```
407+
408+
Let's configure DNSMasq and `appjail-dns` rc script.
409+
410+
```bash
411+
sysrc appjail_dns_enable="YES"
412+
sysrc dnsmasq_enable="YES"
413+
sysrc dnsmasq_conf="/usr/local/share/appjail/files/dnsmasq.conf"
414+
touch /var/tmp/appjail-hosts
415+
service dnsmasq start
416+
service appjail-dns start
417+
cp /usr/local/etc/appjail/resolv.conf /etc/resolv.conf
418+
chflags schg /etc/resolv.conf
419+
```
420+
421+
To test our configuration, simply use a jail's name as the hostname and try to resolve it using a tool like `host(1)`.
422+
423+
```console
424+
# host tautulli
425+
tautulli has address 10.0.0.3
426+
```
427+
428+
If you've created a jail and then run `host(1)` before the `appjail-dns` rc script detects the changes, you may receive an `NXDOMAIN` error. If you don't want to wait, simply restart the `appjail-dns` rc script.
429+
430+
```console
431+
# service appjail-dns restart
432+
Stopping appjail_dns.
433+
Waiting for PIDS: 89362.
434+
AppJail log file (DNS): /var/log/appjail.log
435+
Starting appjail_dns.
436+
# host tautulli
437+
tautulli has address 10.0.0.3
438+
```
439+
440+
!!! note
441+
Please note that this only works with Virtual Networks.
442+
443+
See also: [DNS on AppJail Handbook](https://appjail.readthedocs.io/en/latest/networking/DNS/).
444+
138445
---
139446

140-
### Interactive Configuration
447+
## Interactive Configuration
141448

142449
<div class="placeholder-settings-panel"></div>
143450

144-
### Next Steps
451+
## Next Steps
145452
- [Available Images](../images/index.md) — Full image fleet
146453
- [Permissions](permissions.md) — Understanding PUID/PGID
147-
- [Networking](networking.md) — Port forwarding vs host network
454+
- [Networking](networking.md) — Port forwarding vs host network

0 commit comments

Comments
 (0)