JSON Basics for Self-Hosting

What Is JSON?

JSON (JavaScript Object Notation) is a text format for structured data. You’ll encounter it constantly in self-hosting: Docker daemon configuration, API responses, application settings, Portainer templates, and webhook payloads.

Unlike YAML (which Docker Compose uses), JSON uses braces and brackets instead of indentation. It’s stricter — no comments allowed, trailing commas break parsing — but also more universal. Every programming language and API speaks JSON.

Prerequisites

JSON Syntax

Data Types

JSON has six data types:

{
  "string": "hello world",
  "number": 42,
  "float": 3.14,
  "boolean": true,
  "null_value": null,
  "array": [1, 2, 3],
  "object": {
    "nested_key": "nested_value"
  }
}
TypeExampleNotes
String"hello"Always double-quoted. No single quotes.
Number42, 3.14No quotes. Integers and floats.
Booleantrue, falseLowercase. No quotes.
NullnullLowercase. Represents “no value.”
Array[1, 2, 3]Ordered list. Can contain any types.
Object{"key": "val"}Key-value pairs. Keys must be strings.

Objects (Key-Value Pairs)

Objects are the most common JSON structure. They use curly braces {}:

{
  "hostname": "server01",
  "ip": "192.168.1.100",
  "port": 8080,
  "ssl": true
}

Rules:

  • Keys must be double-quoted strings
  • Key-value pairs separated by commas
  • No trailing comma after the last pair
  • Keys must be unique within the same object

Arrays (Ordered Lists)

Arrays use square brackets []:

{
  "dns_servers": ["1.1.1.1", "8.8.8.8", "9.9.9.9"],
  "ports": [80, 443, 8080],
  "services": [
    {"name": "nextcloud", "port": 8080},
    {"name": "jellyfin", "port": 8096}
  ]
}

Nesting

JSON structures can be nested to any depth:

{
  "server": {
    "network": {
      "interfaces": [
        {
          "name": "eth0",
          "addresses": ["192.168.1.100/24"]
        }
      ]
    }
  }
}

JSON in Self-Hosting

Docker Daemon Configuration

The Docker daemon reads /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "dns": ["1.1.1.1", "8.8.8.8"],
  "default-address-pools": [
    {
      "base": "172.17.0.0/12",
      "size": 24
    }
  ]
}

After editing, restart Docker:

sudo systemctl restart docker

A single syntax error in this file prevents Docker from starting. Always validate before restarting.

API Responses

Most self-hosted apps expose REST APIs that return JSON:

# Nextcloud status
curl -s https://cloud.example.com/status.php | jq .

# Portainer API
curl -s -H "X-API-Key: your-key" \
  https://portainer.example.com/api/endpoints | jq .

Application Configuration

Many apps store settings in JSON files:

{
  "server": {
    "host": "0.0.0.0",
    "port": 3000
  },
  "database": {
    "type": "postgres",
    "host": "db",
    "port": 5432,
    "name": "myapp",
    "user": "appuser",
    "password": "change-me"
  },
  "features": {
    "registration": false,
    "api_access": true
  }
}

Working with JSON on the Command Line

jq — The JSON Swiss Army Knife

jq is the essential tool for parsing JSON on the command line.

# Install jq
sudo apt install jq  # Debian/Ubuntu
sudo dnf install jq  # Fedora

# Pretty-print JSON
echo '{"name":"test","port":8080}' | jq .

# Extract a specific field
echo '{"name":"test","port":8080}' | jq '.name'
# Output: "test"

# Extract from nested objects
echo '{"server":{"port":8080}}' | jq '.server.port'
# Output: 8080

# Extract from arrays
echo '{"dns":["1.1.1.1","8.8.8.8"]}' | jq '.dns[0]'
# Output: "1.1.1.1"

# Filter arrays
echo '[{"name":"a","up":true},{"name":"b","up":false}]' | \
  jq '.[] | select(.up == true)'

Validating JSON

Check if a file is valid JSON before applying it:

# Using jq
jq . /etc/docker/daemon.json > /dev/null
# Exit code 0 = valid, non-zero = invalid

# Using python
python3 -m json.tool /etc/docker/daemon.json > /dev/null

Modifying JSON Files

Edit JSON with jq:

# Add a key
jq '. + {"new_key": "value"}' config.json > config_new.json

# Change a value
jq '.port = 9090' config.json > config_new.json

# Delete a key
jq 'del(.old_key)' config.json > config_new.json

# Add to an array
jq '.dns += ["9.9.9.9"]' daemon.json > daemon_new.json

Always write to a new file first, verify, then replace the original.

JSON vs YAML

You’ll use both formats in self-hosting. Here’s when you see each:

FormatUsed InCommentsTrailing Commas
JSONDocker daemon, APIs, app configsNot allowedNot allowed
YAMLDocker Compose, Ansible, K8sAllowedN/A (no commas)

The same data in both formats:

JSON:

{
  "services": {
    "web": {
      "image": "nginx:1.25",
      "ports": ["80:80"]
    }
  }
}

YAML:

services:
  web:
    image: nginx:1.25
    ports:
      - "80:80"

YAML is generally easier to read and write. JSON is more universal and less ambiguous.

Common Mistakes

Trailing Commas

{
  "name": "test",
  "port": 8080,
}

That trailing comma after 8080 is invalid JSON. Remove it. This is the single most common JSON syntax error.

Single Quotes

{
  'name': 'test'
}

JSON requires double quotes. Single quotes are invalid. Always use ".

Unquoted Keys

{
  name: "test"
}

Keys must be double-quoted strings. This isn’t JavaScript — it’s JSON.

Comments

{
  "port": 8080  // web server port
}

JSON does not support comments. Some tools (like VS Code) allow JSONC (JSON with Comments), but standard JSON parsers will reject this. Use a separate documentation file or inline the explanation in key names.

Next Steps

FAQ

Can I add comments to JSON files?

Standard JSON does not support comments. Some tools support JSONC (JSON with Comments), but never rely on this for configs that other tools will read. If you need to document a JSON config, keep a separate README or use descriptive key names.

What’s the difference between JSON and JSON5?

JSON5 is an extension that allows comments, trailing commas, single quotes, and unquoted keys. Some newer tools support it, but most self-hosted applications expect standard JSON. Stick with standard JSON unless the app’s documentation explicitly mentions JSON5 support.

How do I convert between JSON and YAML?

Use yq (a YAML processor similar to jq): yq -o=json '.' file.yaml converts YAML to JSON. For the reverse: yq -o=yaml '.' file.json. Install with snap install yq or from the GitHub releases.

Why does Docker use JSON for daemon config but YAML for Compose?

Historical reasons. The Docker daemon predates Docker Compose and was configured with JSON from the start. Compose was designed for human-readability and chose YAML. Both formats work fine for their respective purposes.

Comments