Connector
Query a private database without exposing it, using an agent that runs in your own network.
Connector
Most databases worth asking questions about are not reachable from the internet. They listen on loopback, sit inside a VPC, or hide behind NAT. The Connector lets you use one anyway, without exposing it.
You run a small agent next to your database. It dials out to Answerplane over TLS and keeps that connection open. Queries travel back down it.
- No inbound port. No firewall rule, no public database, no allowlist to maintain.
- Your credentials stay yours. The connection string lives in the agent's configuration, inside your network. Answerplane stores only which connector and which datasource to use.
- Read-only. The agent refuses writes before they reach your database, on top of the read-only role you give it.
Install
Create the connector in Settings → Connectors, or from the API:
curl -X POST https://api.answerplane.com/api/v1/connectors \
-H "Authorization: Bearer $ANSWERPLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"production"}'The response contains a token, shown once. Then run this on any host that can reach your database:
docker run -d --name answerplane-connector --restart unless-stopped \
-e ANSWERPLANE_TUNNEL_URL=wss://api.answerplane.com/api/v1/connectors/tunnel \
-e ANSWERPLANE_CONNECTOR_TOKEN=apc_your_token_here \
-e ANSWERPLANE_DATASOURCES='{"mydb":{"dsn":"postgresql://readonly:password@localhost:5432/mydb"}}' \
answerplane/connector:latestThat is the whole install. The connector appears as online within a few seconds, and the datasources it advertises become available when you add a database.
The image is published for linux/amd64 and linux/arm64, so it runs on ordinary servers and on Graviton or Ampere instances alike. Pin a version instead of latest if you would rather control upgrades yourself:
docker pull answerplane/connector:1.0.0Prefer not to run containers? See running without Docker below.
Prepare a read-only user
Give the agent an account that cannot change anything. For PostgreSQL:
CREATE ROLE answerplane LOGIN PASSWORD 'choose-a-strong-password';
GRANT CONNECT ON DATABASE mydb TO answerplane;
GRANT USAGE ON SCHEMA public TO answerplane;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO answerplane;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO answerplane;MySQL, SQL Server, Oracle, MongoDB and the warehouses follow the same principle: grant read, grant nothing else.
Several databases, one connector
One agent can serve many datasources. Name each one; those names are what you pick from when adding a database.
-e ANSWERPLANE_DATASOURCES='{
"billing": {"dsn":"postgresql://readonly:[email protected]:5432/billing"},
"analytics": {"dsn":"mysql://readonly:[email protected]:3306/analytics"},
"events": {"dsn":"mongodb://readonly:[email protected]:27017/events"}
}'If a password contains characters that are awkward in a URL, pass it separately instead. Explicit fields take precedence over the DSN:
{"billing": {"db_type": "postgresql", "host": "10.0.0.5", "database": "billing",
"username": "readonly", "password": "p@ss/word"}}Supported databases
Everything Answerplane supports directly also works through a connector: PostgreSQL, MySQL and MariaDB, SQL Server, Oracle, SQLite, MongoDB, Redshift, BigQuery, Snowflake, Databricks and ClickHouse.
Settings
| Variable | Required | Purpose |
|---|---|---|
ANSWERPLANE_TUNNEL_URL |
yes | wss://api.answerplane.com/api/v1/connectors/tunnel |
ANSWERPLANE_CONNECTOR_TOKEN |
yes | the token from enrollment |
ANSWERPLANE_DATASOURCES |
yes | JSON map of name to connection details |
ANSWERPLANE_MAX_ROWS |
no | row ceiling per query, default 50000 |
ANSWERPLANE_MAX_QUERY_SECONDS |
no | query timeout ceiling, default 120 |
ANSWERPLANE_MAX_CONCURRENT |
no | concurrent queries, default 8 |
ANSWERPLANE_LOG_LEVEL |
no | default INFO |
The agent shares your database with your own application, so the concurrency and row ceilings are deliberately conservative. Raise them only if you know the headroom is there.
Running without Docker
The agent is a normal Python process, so it runs anywhere Python 3.12+ does.
git clone https://github.com/edihasaj/answerplane.git /opt/answerplane-connector
cd /opt/answerplane-connector
python3 -m venv venv
./venv/bin/pip install websockets structlog pydantic pydantic-settings \
cryptography aiomysql tenacity asyncpg aiosqlite boto3 aioodbcThen run ./venv/bin/python -m connector with the same environment variables, under systemd, supervisor, or whatever you already use. That is roughly a 60 MB install, and the agent idles around 40 MB resident.
On Debian and Ubuntu you also need unixodbc, even for a PostgreSQL-only host: the driver package imports the SQL Server driver eagerly, and it will not start without libodbc.so.2.
sudo apt-get install -y unixodbcIf your service unit uses ProtectHome=true, add "ssl_mode": "disable" to any datasource pointing at 127.0.0.1. Otherwise the PostgreSQL client probes ~/.postgresql/ for a client certificate, the sandbox denies it, and the driver reports that as a failed connection.
Operating it
Check status in Settings → Connectors, or:
curl -s https://api.answerplane.com/api/v1/connectors \
-H "Authorization: Bearer $ANSWERPLANE_API_KEY"Logs live in the container:
docker logs -f answerplane-connectorIf the connection drops, the agent reconnects on its own with backoff. Restarting it is always safe.
To upgrade, pull and recreate:
docker pull answerplane/connector:latest
docker rm -f answerplane-connector
# then re-run the install command aboveRevoking
Deleting a connector invalidates its token immediately and closes any live connection. The token cannot be reused; issue a new connector to reconnect.
Troubleshooting
Stays offline. Check the host can reach api.answerplane.com on 443 outbound. The agent logs the reason it could not connect.
Rejected token. Tokens are shown once at enrollment. If it was lost, create a new connector.
Queries fail with a read-only error. Expected: the agent refuses anything that is not plainly a read. If a legitimate query is being caught, rephrase it as a SELECT.
Cannot reach the database. The agent connects from wherever it runs. If it is in a container, localhost means the container, not the host. Use the host's address, or run with --network host.