Turn Container Logs into queryable tables.
Every container the daemon runs writes a log. Query all of them at once — carrying the container's real name, its image and its Compose service, instead of a directory full of 64-character ids.
Use the results in Excel, Google Sheets, AI tools and your apps through the Database REST API.
Every connector type is on Free. Start with 2 data sources and 1 agent.
Read, organize and query on your machine.
Read-only queries · outbound connectionSource files stay in place. Your tools receive query results.
Query Streams turns Docker's json-file logs, the docker logs exports you save by hand, and Portainer's own server log into live SQL tables readable from Microsoft Excel, Google Sheets, AI assistants over MCP, the REST API and Power BI. The hard part it solves is identity: on disk these files are named by container id, so the agent resolves each one back to the container's real name, image and Compose service before the rows land. Nothing is uploaded - the agent reads the files on the host and only query results leave your network.
- Real container names, not ids
- Image and Compose service per row
- stdout and stderr stay separate
Connect Container Logs with the right settings.
Keep the connection on the Network Agent. These are the details specific to Container Logs.
- Folder
- The daemon's log directory on the container host, or a folder holding exported logs
- Sources
- Docker json-file logs, saved docker logs exports, and Portainer's server log
- Identity
- Container name, image and Compose service resolved from the daemon, so rows are not keyed by a 64-character id
- Rotation
- Followed as the daemon rotates, plus the ten named rotation patterns for exported files
- Unmatched lines
- Kept verbatim in events_raw_lines, never dropped
- Credentials
- None - the agent reads the files on the host in place
- SQL dialect
- DuckDB - standard SQL, nothing container-specific to learn
The agent reads the log files the daemon has already written. It does not attach to containers, does not hold the daemon socket open for streaming, and never writes to or rotates those files itself - so a connector left running cannot change how the host behaves.
Built for how Container Logs works.
The hard part is identity, not parsing
Docker's json-file driver writes each container's log into a directory named after its 64-character id. Parsing the JSON is trivial; knowing which container it was is not, and it is the thing that makes the table usable.
- Each row carries the container's real name, its image and its Compose service, resolved from the daemon rather than guessed from the path.
- That means "errors in the api service this morning" is a WHERE clause, not a lookup table you maintain by hand.
- The stream is kept too, so stdout and stderr stay distinguishable instead of being merged into one column of text.
- A container that has been removed keeps the rows it already produced - the history does not disappear with the container.
-- Which services are noisiest right now
SELECT service, stream, COUNT(*) AS lines
FROM fileset.events
WHERE stream = 'stderr'
GROUP BY service, stream
ORDER BY lines DESCThree things, not one
The daemon's json-file logs are the main event, but they are not the only container log worth querying: a docker logs export saved by hand has a different shape, and Portainer writes a server log of its own. All three have profiles, so you choose what you are reading rather than hoping one parser copes with all of it.
The formats and behavior, in detail.
Open the reference you need for Container Logs.
One folder of container logs becomes one table.
One folder of container logs becomes one table.
Query Streams reads the files the daemon already writes, pins the format, and gives you typed columns with the container named beside every row. No sidecar, no shipping pipeline, no index to maintain — and a line the parser cannot read is kept as a row with a reason rather than silently dropped.
Rotations, compressed archives and re-read files — handled
SELECT app,
compose_service,
count(*) AS lines
FROM fileset.events
WHERE stream = 'stderr'
GROUP BY app, compose_service
ORDER BY lines DESCJoin it to your application database in the same query
Files the daemon already writes, now a table you can report on
Two tools, three formats, no sidecar
Two tools, three formats, no sidecar
Each card names the exact files Query Streams reads and where they live. You do not change your logging driver to make this work — json-file is the Docker default, and the default is the supported one.
Supported container logs
3 formats · 2 tools- One
{"log","stream","time"}envelope per line, written by the daemon - The container name comes from Docker’s own
config.v2.jsonbeside the log - Image and Compose project/service ride along as columns
- Usually
/var/lib/docker/containers - Appends; numbered rotations and
.gzfollowed
- Files you saved with
docker logs --timestamps - An RFC 3339 nanosecond stamp, then the container’s own line
- Container banner lines are skipped by declaration, not by luck
- Any folder you keep the exports in
- One file per service; the name comes from the file stem
portainer.logor the container’s own output- ANSI colour codes stripped before parsing, so a level is a level
- The
key=valuetail becomes columns; the remainder lands indata - The embedded tunnel server’s differently shaped lines are read too
- Minute-resolution stamps, read as UTC
New container log formats are added with each Agent release. Because Agents keep themselves up to date, a format added later shows up in your connector without anyone visiting a server.
docker logs exports off a Swarm host, and Portainer CE 2.39.1’s server log. One thing to know up front: on Docker Desktop for Windows and Mac the json-file logs live inside its virtual machine, where an Agent running as a service cannot reach them. Export them with docker logs --timestamps and use the second profile.
Containers in every region, logs read where they sit
Containers in every region, logs read where they sit
Containers rarely all run in one place. A host in the office, a Swarm cluster in one cloud region, a single box in another country. Each location runs a Network Agent that reads the log files where they already are and dials out to Query Streams. As far as your firewall is concerned that is an ordinary outbound connection — there is nothing to open and no VPN to build.
docker logs export per service
dials out
Three outbound connections, one place to query them — no inbound port, no VPN, no firewall change
One Agent per location
An Agent covers a whole site rather than a single folder: each log format you want there becomes its own File Set connector on that same Agent. One Agent per location is the normal shape. The Free plan runs a single Agent and paid plans run more.
1 site = 1 Agent = many connectors
Nothing to open
The Agent makes one encrypted outbound connection, and the request out and the rows back both travel over it. No inbound port, no VPN, no firewall change to go and ask for — and credentials stay inside your network.
one connection, both directions
One query across sites
Every source in a federated query names its own Agent, so a single statement can read a log in one country and a log in another and hand back one result — or join either of them to a database. Included on Business and Enterprise.
2 countries → 1 result set
They keep themselves current
Agents update themselves, so a container log format added in a later release reaches every site without anyone logging into a server to install it. That is what makes the wall above a starting point rather than a fixed list.
new formats arrive with the Agent
Names, clocks and colour codes
Names, clocks and colour codes
A container log is not a text file with a date on it. Three things routinely go wrong when you try to treat one as data, and each is handled here by declaration rather than by guesswork.
Names, not hex ids
Docker names each log folder after the container id, which makes a grouped query unreadable. Query Streams reads the record Docker writes beside the log and takes the real name, keeping the id as its own column.
a3f9c1…-json.log → app = shopfront-api
The daemon’s clock
A container that prints its own timestamp in epoch seconds lands in 1970 if you believe it. The event time is the daemon’s capture time; the container’s own stamp is kept as a column instead of being trusted.
envelope time → event_time
Colour codes in the file
Console loggers write terminal escape sequences straight into the log. They are stripped before parsing, so a level reads as a level rather than as a level wrapped in escape characters — and the original bytes are left untouched.
escape codes removed → level = INF
Nothing counted twice
Each file is fingerprinted by its first 4 KB, so a rotated file is recognised as the same file rather than read again from the top. A rolling duplicate window catches the overlap when a writer replays its tail.
4 KB prefix hash · 20,000-record window
The columns you actually get
The columns you actually get
Not a blob of text with a timestamp beside it. Each format is parsed into typed columns you can filter, group and aggregate on directly — and where a container logs structured JSON, its own fields are unwrapped into columns too.
Every row also carries event_time, the raw stamp it was read from, and the file it came from. Each format adds its identity column — app for json-file and Portainer, container for an export — and json-file rows additionally carry image, compose_project, compose_service and container_id. The single message column on an export is deliberate: the container’s own line is kept whole rather than guessed at.
What this deliberately does not do
What this deliberately does not do
It is not a live tail. Query Streams reads the files as they are on disk when you run the query, so a restart loop shows up as rows rather than as a stream you watch. If you need sub-second alerting on a line as it is printed, a log pipeline is the right tool.
A container’s output is freeform, and that has a consequence worth saying plainly: whatever your application prints is what lands in the table. If it logs a token or a customer email, that string is already in the log file and it will be in the column too. There is no header map to exclude the way there is for a web server log — treat the resulting table with exactly the care you would treat the file.
Two smaller limits, named rather than hidden. On a docker logs export the container’s own line is kept whole in one message column; re-parsing that inner line by its own format is not built yet. And the optional attributes Docker can attach with a logging tag are recognised by shape only — no container in the captured corpus used them, so that path is declared, not proven.
The rest follows the usual design. The Agent connects outbound and reads files; nothing is installed in the container, nothing is written back, the Docker socket and API are never touched, and access is read-only. The retention field starts at 30 days for this preset and is yours to change.
From connection to useful results.
- Install the Network Agent on the container host, where it can see the daemon's log directory.
- Point the connector at it. Every container's json-file log is matched, and you can narrow that with patterns.
- The agent resolves each 64-character directory back to the container it belongs to - real name, image and Compose service - so the rows are readable before you write any SQL.
- Every line becomes a row, with its stream (stdout or stderr) and the daemon's timestamp, and rotation is followed as the daemon rotates.
- Query it. Errors across every container at once, one service in isolation, or a before-and-after around a deploy.
Put your Container Logs results to work.
One connection serves your spreadsheets, apps and AI tools. Share permission to run a saved query; recipients see the allowed results, with the SQL and source login hidden.
For your team, vendors and partners
Pull live Container Logs results straight into a worksheet and refresh them on demand — desktop Excel, Excel Online, Microsoft 365.
Explore ExcelRun a saved Container Logs query from the sidebar and drop the rows into the sheet. Shared collaborators can refresh it themselves.
Explore Google SheetsAsk a question in plain English. Nova already knows your Container Logs tables, writes and runs the SQL, explains the answer and charts it — no query to write, no credentials to hand over.
How Nova worksShare a live Container Logs result with anyone — a colleague, a vendor, a partner — as a bookmarkable grid that re-runs fresh when opened, on a phone, a tablet or a desktop. They see the results, never the SQL or the files.
How the Data Viewer worksFor apps and systems
Publish a Container Logs query as an authenticated JSON endpoint any application can call, with an OpenAPI 3.1 spec and ready-made Postman, Insomnia and Hoppscotch collections. No database port is opened.
Explore REST APIPaste the generated Power Query M into the Power BI Advanced Editor and the report reads live Container Logs results over HTTPS — no ODBC driver, no database port opened.
Explore Power BISync Container Logs rows into an Airtable base on a schedule, or fetch them inside an Airtable automation script.
Explore AirtableFeed a Baserow table from Container Logs over the REST endpoint — self-hosted or Baserow cloud.
Explore BaserowKeep a SeaTable base current with Container Logs data without exporting a file or exposing the database.
Explore SeaTablePush Container Logs results into a Smartsheet grid so plans and reports read from the source system, not last week's export.
Explore SmartsheetBack an Anvil Python app with Container Logs through the REST endpoint instead of embedding database credentials in the app.
Explore AnvilFor AI tools
Give an AI assistant read-only access to Container Logs with the schema it needs to write correct SQL — no credentials in the chat.
Explore MCPDelivered to people
Put a Container Logs query on a schedule and have the rows delivered to Slack, Discord, email or a signed webhook — or hold the message until a row count, threshold or percentage change crosses the line you set.
How alerts and reports workJoin Container Logs to the rest of your data.
Bring databases, business APIs, files and logs into one read-only query. Every connector supports Federated Queries.
Name the connections and tables. Query Streams handles the source queries and combines the results.
Explore Federated Queries-- nothing copied, nothing merged, nothing scheduled
SELECT c.region, COUNT(*) AS orders, SUM(i.amount_due) AS invoiced
FROM containerlogs.fileset.orders1 f
JOIN pg_crm.public.customers2 c ON c.id = f.customer_id
JOIN billing.stripe.invoices3 i ON i.customer = c.stripe_id
GROUP BY c.region
ORDER BY invoiced DESC;Use your own connection, schema, table and column names.
Shared across the connector family.
Every log connector also gives you
- events — one row per parsed line, with the timestamp, the fields the grammar named, and _source_file naming the file it came from.
- events_raw_lines — lines the grammar did not match, kept verbatim rather than dropped, so a format you did not anticipate is a question you can still answer.
- files_current and files_events — the live inventory and the audit trail: what appeared, what rotated, what vanished, and anything held back, with the filename and the reason.
- Rotation is understood, not worked around — ten named rotation patterns plus plain unrotated files, so yesterday's log and its compressed archive are the same table as today's.
Good to know
- Read-only, enforced — one statement at a time, SELECT and friends only. Your logs are never written, moved, rotated or truncated by us.
- Nothing is uploaded — lines are parsed and cached, encrypted, on your own machine beside the agent. Only the result of a query leaves your network.
- Only new bytes are read — the agent resumes where it left off in each file rather than re-reading it, so a busy log does not cost more every day.
- The SQL dialect is DuckDB — the same SQL you would write against any other connector.
Questions about Container Logs
Where can I use Container Logs query results?
Use Container Logs results in Excel, Google Sheets, the Data Viewer, your apps through the Database REST API, and AI tools through MCP or Nova. Saved results also feed supported Automation destinations, reports and alerts. Destination availability and allowances depend on your plan.
Do I need to open an inbound port for Container Logs?
No. Install the Network Agent on a machine that can reach Container Logs. The agent connects outward to Query Streams; no inbound port or VPN is needed. Your source must still allow the agent machine to connect under its own access rules.
Are my Container Logs files uploaded?
The Network Agent reads files where they are and keeps its cache on the agent machine. Source files are not uploaded, moved or rewritten. Only requested query results leave your network over the outbound encrypted connection.
What do I need to connect Container Logs?
Folder: The daemon's log directory on the container host, or a folder holding exported logs. Sources: Docker json-file logs, saved docker logs exports, and Portainer's server log. Identity: Container name, image and Compose service resolved from the daemon, so rows are not keyed by a 64-character id. Rotation: Followed as the daemon rotates, plus the ten named rotation patterns for exported files
Can I join Container Logs to other connected sources?
Yes. Federated Queries support every connector type, including databases, business APIs, files and logs. Name the connections and tables in one read-only query. Query Streams translates the source portions as needed and combines their results. See how Federated Queries work.
Do I have to change my Docker logging driver?
No. json-file is Docker’s default driver, and it is the supported one. If you have never configured logging, the files are already there and already in the right shape.
If you have moved to a different driver, the docker logs --timestamps export is the route in: it works whatever the driver, because the CLI reads through the daemon.
How does it know the container’s name and not just its id?
Docker writes its own record of a container in the same folder as the log. Query Streams reads the name out of that file and uses it as the identity column, with the image and the Compose project and service kept as columns beside it. The 64-character folder id is retained as container_id, so nothing is lost.
If that record is missing, unreadable or does not carry a name, the folder name is used instead and the sweep counts how often that happened — so a fallback shows up as a number you can look at, not as a silent substitution.
I am on Docker Desktop for Windows or Mac. Does this work?
Not against the json-file logs directly. Docker Desktop keeps those inside its own virtual machine, and an Agent running as a service on the host cannot see into it.
The way through is the export profile: docker logs --timestamps <container> redirected to a file, in any folder the Agent can read. That is a supported format in its own right, not a workaround — it is the same route people already use to keep a copy of a log after a container is gone.
Is this real-time?
It is on-demand rather than streaming. The files are read as they stand when the query runs, so new lines appear as your containers write them and the file is picked up — not on a fixed nightly schedule, but not a live tail either.
For asking a question of the last few weeks across every container and getting the answer into a spreadsheet, that is the useful shape and considerably less machinery than a log stack.
What about Kubernetes or Podman?
Neither has a profile of its own today, and we would rather say so than imply coverage. What does apply is the export format: the profile is keyed to the exact shape docker logs --timestamps writes — an RFC 3339 nanosecond stamp, a space, then the container’s own line, one file per service. Tooling that writes that same shape reads with it.
The reliable way to find out for your own files is Test connection, which reports what it recognised before you commit to anything.
My containers log JSON. Do I get their fields as columns?
Yes, for json-file logs. The daemon’s envelope is unwrapped and the container’s own JSON inside it is flattened into columns — level, logger, message, host and service where they are present. Anything that does not map to a named column is kept in a data column rather than discarded, so an unusual field is still queryable.
The one thing not taken from the container’s JSON is the timestamp. That comes from the daemon, because an application’s own stamp can be in any format and getting it wrong silently misplaces the row in time.
What about a line the parser cannot read?
It is kept, not dropped. A line that does not match the pinned format is retained as a raw row with a reason attached, so a malformed entry or an unexpected format change shows up as something you can see and query rather than as a silent gap in your numbers.
Put Container Logs where the work happens.
Start with Free: every connector type, 2 data sources and 1 Network Agent. Add plan allowances as your needs grow.

