On this page
Basefile
Service manifest format for bases
A Basefile is a YAML file named Basefile (no extension)
placed at the root of a service repository. It declares everything
bases needs to install, run, and manage the service:
the container image, the port, environment variables, persistent data directories,
and lifecycle scripts.
Any application that ships a Basefile can be installed on any bases instance with a single command. No manual configuration steps, no per-host documentation — the Basefile is the complete, version-controlled specification.
Quick look
name: myapp
image: images:alpine/3.21
port: 8080
setup:
data:
- data
tasks:
install: |
apk add --no-cache curl
curl -fsSL https://example.com/releases/myapp-linux-amd64 \
-o /usr/local/bin/myapp
chmod +x /usr/local/bin/myapp
start: |
/usr/local/bin/myapp --port 8080
Field reference
Top-level fields
These keys appear at the top level of the Basefile. name,
image, and port are required. All others are optional
unless noted.
name
string
required
The service identifier. Used as the Incus container name, the subdomain prefix
(<name>.<domain>), and the data directory path
(/var/lib/bases/<name>/).
Must match ^[a-z0-9][a-z0-9-]*$ — lowercase alphanumeric
characters and hyphens only. No dots, underscores, or uppercase.
name: my-wiki
description
string
optional
Human-readable description displayed in the bases web UI. Has no effect on behaviour.
description: Self-hosted team chat
version
string
required for URL installs
Git tag to check out before reading the Basefile. Required when installing from a git URL; ignored for local installs. Must be a tag present in the repository at install time.
version: v1.2.0
image
string
required
The Incus container image to use. Must be a valid Incus image reference. Alpine Linux is the recommended base — it produces smaller containers and installs faster than Ubuntu.
image: images:alpine/3.21
port
integer
required
The TCP port the service listens on inside the container. bases configures
Caddy to proxy <name>.<domain> to this port.
port: 8080
env
map
optional
Static environment variables injected into the container at every start. Keys and values are arbitrary strings.
Do not declare OIDC variables here — bases injects them automatically
when setup.auth.type: oidc.
env:
DOCS_DIR: /data/data
PORT: "3000"
logs
list
optional
Absolute paths to log files inside the container. Controls the log viewer in the bases service detail page: 0 entries hides the viewer; 1 entry shows the viewer; 2 or more entries show the viewer with a file selector dropdown.
logs:
- /var/log/myapp.log
health
string
optional
Path to a health-check endpoint inside the container (e.g. /healthz).
Parsed but not yet implemented — reserved for future use.
health: /healthz
setup
The setup block configures bases-managed wiring: authentication,
the service's public URL variable, and persistent data directories.
The entire block is optional — omit it if none of these are needed.
setup.auth.type
string
optional
Authentication type. Accepted values: oidc or empty (default).
When set to oidc, bases registers an OIDC client in Pocket ID
and injects the credentials as environment variables into the container at every
start. The injected variable names default to:
OIDC_ISSUEROIDC_CLIENT_IDOIDC_CLIENT_SECRETOIDC_CALLBACK_URL
Do not declare these in the env block — they are injected automatically.
setup:
auth:
type: oidc
setup.auth.callback_path
string
optional
Override the OIDC redirect URI path. Defaults to /auth/callback.
Only relevant when setup.auth.type: oidc.
setup:
auth:
type: oidc
callback_path: /oauth/callback
setup.auth.env
map
optional
Override the names of the environment variables bases injects for OIDC.
Each key corresponds to a credential; the value is the env var name bases
will use. All default to standard names. Only relevant when
setup.auth.type: oidc.
setup:
auth:
type: oidc
env:
issuer: MY_OIDC_ISSUER
client_id: MY_CLIENT_ID
client_secret: MY_CLIENT_SECRET
callback_url: MY_CALLBACK_URL
setup.base_url
string
optional
The name of the environment variable bases uses to inject the service's
public URL (https://<name>.<domain>). Defaults to
BASE_URL. Override this if your application expects a different
variable name.
setup:
base_url: APP_PUBLIC_URL
setup.data
list
optional
Persistent data directories. For each declared entry, bases creates
/var/lib/bases/<name>/<entry>/ on the host and
bind-mounts it into the container. Directories persist across restarts,
upgrades, and reinstalls. They are not removed when a service is destroyed.
Recognised entry names: data, db,
config, etc.
Each entry supports two YAML forms:
String shorthand — path inside the container defaults to
/data/<name>:
setup:
data:
- data
- db
Long form — explicit container mount path:
setup:
data:
- name: db
path: /var/lib/myapp/db
tasks
Tasks are inline shell scripts run as root inside the container via
sh -c. Values are YAML block scalars (|).
Unknown task keys are rejected at parse time.
tasks.install
string
optional
Runs once, immediately after the container is first created during installation. Use this to install packages, download binaries, run database migrations, write init scripts, and perform any one-time setup.
tasks:
install: |
set -e
apk add --no-cache curl
curl -fsSL https://example.com/releases/myapp-linux-amd64 \
-o /usr/local/bin/myapp
chmod +x /usr/local/bin/myapp
tasks.start
string
optional
Runs each time the service is started or restarted. bases runs it in the
background via nohup and does not wait for it to exit. Use this
to launch the service process.
tasks:
start: |
/usr/local/bin/myapp --port 8080
tasks.backup:*
string
optional, repeatable
Any task key beginning with backup: (e.g. backup:db,
backup:config). bases runs all backup tasks in declaration order
immediately before copying data directories during a backup operation.
tasks:
backup:db: |
myapp dump --output /data/db/backup.sql
backup:config: |
cp -r /data/config /data/data/config-backup
Examples
Minimal
The smallest valid Basefile. Good starting point for a new application.
name: myapp
image: images:alpine/3.21
port: 8080
tasks:
install: |
set -e
apk add --no-cache curl
curl -fsSL https://example.com/releases/myapp-linux-amd64 \
-o /usr/local/bin/myapp
chmod +x /usr/local/bin/myapp
start: |
/usr/local/bin/myapp --port 8080
marki — markdown wiki
Uses env for runtime configuration, string-shorthand
setup.data, and the OpenRC process management pattern on Alpine.
name: marki
description: Minimal markdown wiki server
version: 0.0.3
image: images:alpine/3.21
port: 3000
env:
DOCS_DIR: /data/data
PORT: "3000"
logs:
- /var/log/marki.log
setup:
data:
- data
tasks:
install: |
set -e
apk add --no-cache curl
case "$(uname -m)" in
x86_64) GOARCH=amd64 ;;
aarch64) GOARCH=arm64 ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
curl -fsSL "https://code.bas.es/arne/marki/releases/download/v0.0.3/marki-linux-${GOARCH}" \
-o /usr/local/bin/marki
chmod +x /usr/local/bin/marki
sed -i 's/#rc_sys=""/rc_sys=""/' /etc/rc.conf
sed -i 's/#rc_env_allow="VAR1 VAR2"/rc_env_allow="DOCS_DIR PORT"/' /etc/rc.conf
cat > /etc/init.d/marki << 'EOF'
#!/sbin/openrc-run
name="marki"
description="Minimal markdown wiki server"
command="/usr/local/bin/marki"
command_background="yes"
pidfile="/run/marki.pid"
output_log="/var/log/marki.log"
error_log="/var/log/marki.log"
EOF
chmod +x /etc/init.d/marki
rc-update add marki default
start: |
rc-service marki start
talk — team chat
Uses setup.auth.type: oidc for Pocket ID authentication and
long-form setup.data with an explicit container path.
name: talk
description: Self-hosted team chat
version: v0.0.5
image: images:alpine/3.21
port: 8080
logs:
- /var/log/talk.log
setup:
auth:
type: oidc
callback_path: /auth/callback
data:
- name: db
path: /var/lib/talk
tasks:
install: |
set -e
apk add --no-cache curl
case "$(uname -m)" in
x86_64) GOARCH=amd64 ;;
aarch64) GOARCH=arm64 ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
curl -fsSL "https://code.bas.es/arne/talk/releases/download/v0.0.5/talk-linux-${GOARCH}" \
-o /usr/local/bin/talk
chmod +x /usr/local/bin/talk
sed -i 's/#rc_sys=""/rc_sys=""/' /etc/rc.conf
sed -i 's/#rc_env_allow="VAR1 VAR2"/rc_env_allow="OIDC_ISSUER OIDC_CLIENT_ID OIDC_CLIENT_SECRET OIDC_CALLBACK_URL"/' /etc/rc.conf
cat > /etc/init.d/talk << 'EOF'
#!/sbin/openrc-run
name="talk"
description="Self-hosted team chat"
command="/usr/local/bin/talk"
command_args="serve"
command_background="yes"
pidfile="/run/talk.pid"
output_log="/var/log/talk.log"
error_log="/var/log/talk.log"
EOF
chmod +x /etc/init.d/talk
rc-update add talk default
start: |
rc-service talk start
tub — YouTube feed manager
Uses OIDC auth together with a custom data path. The OpenRC service definition
passes DB_PATH via command_env rather than the
Basefile env block — a valid pattern when the variable is only
needed inside the init script.
name: tub
description: Personal YouTube video feed manager
version: v0.1.6
image: images:alpine/3.21
port: 8080
logs:
- /var/log/tub.log
setup:
auth:
type: oidc
callback_path: /auth/callback
data:
- name: db
path: /var/lib/hosty/tub/db
tasks:
install: |
set -e
apk add --no-cache curl
case "$(uname -m)" in
x86_64) GOARCH=amd64 ;;
aarch64) GOARCH=arm64 ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
curl -fsSL "https://code.bas.es/arne/tub/releases/download/v0.1.6/tub-linux-${GOARCH}" \
-o /usr/local/bin/tub
chmod +x /usr/local/bin/tub
sed -i 's/#rc_sys=""/rc_sys=""/' /etc/rc.conf
sed -i 's/#rc_env_allow="VAR1 VAR2"/rc_env_allow="OIDC_ISSUER OIDC_CLIENT_ID OIDC_CLIENT_SECRET OIDC_CALLBACK_URL BASE_URL"/' /etc/rc.conf
cat > /etc/init.d/tub << 'EOF'
#!/sbin/openrc-run
name="tub"
description="Personal YouTube video feed manager"
command="/usr/local/bin/tub"
command_background="yes"
pidfile="/run/tub.pid"
command_env="DB_PATH=/var/lib/hosty/tub/db/tub.db"
output_log="/var/log/tub.log"
error_log="/var/log/tub.log"
EOF
chmod +x /etc/init.d/tub
rc-update add tub default
start: |
rc-service tub start