On this page

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_ISSUER
  • OIDC_CLIENT_ID
  • OIDC_CLIENT_SECRET
  • OIDC_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