Ollama, Qdrant and Redis in Docker have no authentication by default
Bind an AI dev environment's ports to 127.0.0.1, and check your coding agent's sandbox and MCP approval defaults
On this page
A local AI development stack often combines Ollama for local models, Qdrant and Redis for retrieval-augmented generation (RAG), and a coding agent such as Claude Code or Codex CLI that edits files and runs commands, all on the same machine. According to the official docs, when Ollama, Qdrant and Redis run in Docker, their default configurations do not require authentication, and when Docker Compose publishes a port without a host IP, that port is open on every network interface. This article rewrites the “practical AI development environment setup guide” note first published in Korean on 2026-06-16, whose Compose example was exactly this setup. Instead of ranking tools, this version keeps only defaults that can be confirmed in today's official docs.
Summary
host_ip is setevery network interface · Compose file reference
Ollama, Qdrant, Redis · each project's official docs
Codex CLI's sandbox is on by default with network off · checked 2026-10-06
"127.0.0.1:6333:6333". Docker's docs call port publishing “insecure by default,” and the official Ollama, Qdrant and Redis images accept requests without authentication. Binding to 127.0.0.1 does not stop a coding agent on the same machine from reaching these services, though. So set a Qdrant API key and a Redis password, and check what you are allowing when you open localhost access in an agent sandbox or approve a repository's .mcp.json.
Terms used here
| Term | Meaning |
|---|---|
| Port publishing | Mapping a port inside a container to a port on the host machine, set with docker run -p or Compose's ports: |
| Bind address | The network address a server accepts connections on. 0.0.0.0 means every network interface on the machine; 127.0.0.1 (loopback) means the machine itself only |
| Protected mode | A Redis feature that makes a Redis instance without a password refuse connections from outside loopback. The official Redis Docker image turns it off |
| MCP (Model Context Protocol) | An open specification for how AI agents reach external tools and data. The side that provides tools is the MCP server; the agent side is the client. This article follows the 2026-07-28 revision of the specification |
| Project-scoped MCP server | In Claude Code, an MCP server configuration written to .mcp.json at the repository root and shared with the team. Personal configurations are stored in ~/.claude.json |
| Sandbox | An operating-system-level limit on which files and network destinations the shell commands run by a coding agent can reach |
Ports exposed by the June note's Compose example
The June note included an example that started Ollama, Qdrant, PostgreSQL and Redis from one Compose file. All five ports were published without a host IP, as in "11434:11434". The Compose file reference says that if no host IP is set, the port “binds to all network interfaces (0.0.0.0).” The default for each service is below.
| Service (port) | Default authentication | Official documentation |
|---|---|---|
| Ollama (11434) | None | Authentication docs: “The local API at http://localhost:11434 does not require authentication.” The desktop install binds to 127.0.0.1 only, but the official image's Dockerfile sets OLLAMA_HOST=0.0.0.0:11434, and Ollama's own Docker docs use -p 11434:11434 |
| Qdrant (6333 REST, 6334 gRPC) | None | Security docs: “By default, an open source Qdrant deployment accepts requests from anyone who can reach it.” A self-hosted Qdrant binds to all interfaces, and the docs recommend binding to 127.0.0.1 for local development |
| Redis (6379) | None | Official image docs: protected mode is off so other containers can reach Redis easily, so publishing the port outside the host with -p leaves it “open without a password to anyone” |
| PostgreSQL (5432) | Password required | The official image requires POSTGRES_PASSWORD and asks for the password on connections from another host or container. The June note, however, wrote ai_password directly into the Compose file and into DATABASE_URL in .env.example |
“Open on every interface” does not by itself mean open to the internet. Whether another machine can connect to the port depends on the host's network and firewall. The docs give these conditions.
- Docker Engine (Linux): the port publishing docs say “Publishing container ports is insecure by default” and explain that a published port becomes reachable not only from the Docker host but from the outside world. According to the firewall docs, Docker's forwarding rules apply before Ubuntu's ufw rules, so a port you thought ufw was blocking may still be open.
- Docker Desktop (macOS, Windows): the networking docs say
docker run -plistens on all interfaces (0.0.0.0) by default. You can change the default to localhost in Docker Desktop's network settings, and host firewalls can filter inbound connections bycom.docker.backend. - A caveat from Qdrant's docs: a local development machine likely has a firewall that blocks public access, but a public VPS or dedicated server may not. Copying the same Compose file to a cloud server raises the risk.
A Compose example that opens ports on 127.0.0.1 only
This keeps the June note's setup and changes only what the docs recommend. Secrets are read from a .env file in the same folder; only the Redis password lives in a config file (./redis/redis.conf). We did not run this stack while preparing the article, but each line follows the format in the relevant official docs.
services:
ollama:
image: ollama/ollama
ports:
- "127.0.0.1:11434:11434"
volumes:
- ollama:/root/.ollama
qdrant:
image: qdrant/qdrant
ports:
- "127.0.0.1:6333:6333"
- "127.0.0.1:6334:6334"
environment:
QDRANT__SERVICE__API_KEY: ${QDRANT_API_KEY}
volumes:
- qdrant:/qdrant/storage
postgres:
image: postgres:18
ports:
- "127.0.0.1:5432:5432"
environment:
POSTGRES_USER: ai
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: aidb
volumes:
- postgres:/var/lib/postgresql
redis:
image: redis:8
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
ports:
- "127.0.0.1:6379:6379"
volumes:
- ./redis:/usr/local/etc/redis
volumes:
ollama:
qdrant:
postgres:
# .env (keep out of git)
QDRANT_API_KEY=a_long_random_string
POSTGRES_PASSWORD=a_long_random_string
# ./redis/redis.conf (keep out of git)
requirepass a_long_random_string
- Binding to 127.0.0.1: Docker's docs say that adding
127.0.0.1(or::1) to the publish flag means only the Docker host can reach the port. They also warn that in Docker Engine releases older than 28.0.0, other machines on the same local network segment (the same layer-2 network, for example machines on the same switch with no router between them) could still reach ports published to localhost. Check your Engine version withdocker versionfirst. - Ports used only between containers do not need publishing. According to the Compose networking docs, services in the same Compose file reach each other on the default network by service name (
qdrant:6333). The host port is used only for access from outside the Compose default network. If your app runs on the host withuv run, publish to 127.0.0.1 as above. - Qdrant API key: once
QDRANT__SERVICE__API_KEYis set, every request needs anapi-keyheader. Clients that only query can get a separate read-only key (QDRANT__SERVICE__READ_ONLY_API_KEY, since 1.7.0). Qdrant's docs say to use TLS as well, because a key sent over an unencrypted connection can leak. If you plan to open Qdrant beyond the host, you need TLS. - Redis password: the official image docs recommend setting a password through a config file if you plan to expose Redis, and describe mounting a folder containing
redis.confat/usr/local/etc/redisand passing it toredis-server. As in the example, put the password on arequirepassline in./redis/redis.confand keep that file out of git like.env. Without that line, Redis accepts connections without a password even on 127.0.0.1. - Volume path for PostgreSQL 18: starting with 18, the official image changed
PGDATAto/var/lib/postgresql/18/dockerand declares its volume at/var/lib/postgresql. If you usepostgres:16as the June note did, mount at the old location (/var/lib/postgresql/data) or the data will not persist. - Ollama on macOS: Ollama's macOS docs say Apple M-series machines get both CPU and GPU support, while its Docker docs only cover CPU-only runs and NVIDIA and AMD GPUs. On macOS, installing the app fits the docs better. The app binds to 127.0.0.1, so you can drop the
ollamaservice from the Compose file.
After starting the stack, check that the PORTS column of docker compose ps starts with the loopback address, as in 127.0.0.1:6333->6333/tcp. Any line starting with 0.0.0.0: is a port open on every interface.
How a coding agent on the same machine reaches these services
Binding to 127.0.0.1 blocks other machines, but a coding agent's tests and scripts still run locally and can reach those services. The two agents have different defaults (official docs, checked 2026-10-06).
| Item | Claude Code | Codex CLI |
|---|---|---|
| Shell command sandbox by default | Off. Turn it on with /sandbox or sandbox.enabled: true | On. Commands run in an OS-enforced sandbox |
| Writes inside the sandbox | The working directory, a per-user temp directory, and directories you have added | Limited to the active workspace |
| Reads inside the sandbox | Most of the machine. The docs say credential files such as ~/.ssh and ~/.aws/credentials are readable by default | Not checked for this article |
| Network inside the sandbox | No direct route out; traffic goes through a proxy that checks an allowed-domain list, which starts empty | Command network access is off by default |
| Access to localhost services | On macOS you must turn on network.allowLocalBinding: true, which then allows connections to any port on localhost. On Linux and WSL2, a sandboxed command's localhost is separate from the host's | With network turned on and network_proxy enabled, the default allow_local_binding = false blocks loopback and private addresses. To let a command reach one local target, add an allow rule for that exact local IP or for localhost |
| When the sandbox cannot start | Runs commands without the sandbox by default. sandbox.failIfUnavailable: true makes it exit at startup instead | Not checked for this article |
allowLocalBinding is on in macOS, “a localhost service that doesn't require authentication … can then act for the command outside the sandbox.” The docs' example is a debugger. Ollama, Qdrant and Redis without authentication carry the same risk: a command inside the sandbox could delete a Qdrant collection or flush Redis. If you plan to turn this setting on, set the Qdrant API key and Redis password first.
One change in the Codex docs as of 2026-10-06: approval_policy = "untrusted" has been retired, and if the value is still in a config file, Codex may fail to start. The docs say you can keep the stricter per-command approval behavior by adding trust_level = "untrusted" to a project entry in ~/.codex/config.toml instead.
Defaults to check when adding MCP servers
Claude Code's MCP scopes and approval
| Scope | Loads in | Shared with team | Stored in |
|---|---|---|---|
| local (default) | Current project only | No | ~/.claude.json |
| project | Current project only | Yes, through version control | .mcp.json at the repository root |
| user | All your projects | No | ~/.claude.json |
Claude Code MCP docs, checked 2026-10-06. The latest version at that time was 2.1.290 (2026-10-05).
- The approval prompt appears only in interactive sessions. The docs say Claude Code asks for approval in interactive sessions before using project-scoped servers from
.mcp.json. Inclaude -pruns, Agent SDK sessions, and cloud sessions, Claude Code cannot show that prompt, so it loads those servers without asking. If CI or automation works on repositories you do not control, block servers withdisabledMcpjsonServers, or name the only servers to use with--strict-mcp-configand--mcp-config. - A repository cannot approve its own servers. Since 2.1.196, until you accept the workspace trust dialog, Claude Code ignores
enableAllProjectMcpServersandenabledMcpjsonServerscommitted in the repository's.claude/settings.json. - Keep API keys out of
.mcp.json..mcp.jsonsubstitutes environment variable values for${VAR}and${VAR:-default}incommand,args,env,urlandheaders. Write, for example,"Authorization": "Bearer ${API_KEY}"and keep the value in each person's environment.
MCP tool definitions: the field name in the June note's example
The June note's MCP tool example used input_schema. The MCP specification's field name is inputSchema. input_schema belongs to the tool format of Anthropic's Messages API and does not appear in the tools page of the MCP specification (2026-07-28 revision). In the specification's format, the example becomes:
{
"name": "search_documents",
"description": "Searches a document collection for relevant evidence. Does not delete or modify anything.",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"collection": {"type": "string"},
"top_k": {"type": "integer", "minimum": 1, "maximum": 20}
},
"required": ["query", "collection"]
},
"annotations": {"readOnlyHint": true}
}
- Annotations (
annotations) are optional and only hints. A tool definition requiresinputSchema, but annotations are optional. In the specification's schema,readOnlyHintdefaults tofalse, and when a tool is not read-only,destructiveHintdefaults totrue. The specification says clients MUST treat annotations as untrusted unless they come from trusted servers. - Codex uses these annotations in approval decisions. According to the Codex docs, MCP tool calls always require approval when the tool advertises a destructive annotation. If it also advertises a read annotation, the read annotation takes priority.
- Human confirmation is the specification's recommended default. The specification's tools page says there SHOULD always be a human in the loop with the ability to deny tool invocations. Splitting read tools and write tools into separate servers or separate keys lets you narrow permissions on the server side as well, as with Qdrant's read-only key above.
What changed from the June note
| June note | This version | Why |
|---|---|---|
| A “first choice” tool table per area (LangGraph, Qdrant, FastAPI, Next.js and others) | Removed | No comparison or measurement backed the ranking |
| Recommended RAM of 16 GB, 32 GB and 64 GB | Removed | No source was given, and the figure depends on the model and quantization |
| Compose example for Ollama, Qdrant, PostgreSQL and Redis | Ports bound to 127.0.0.1; secrets moved to .env and redis.conf | The official docs in “Ports exposed by the June note's Compose example” |
input_schema in the MCP tool schema example | Changed to inputSchema, with an annotation example added | MCP specification, 2026-07-28 revision |
| Homebrew, mise, uv and pnpm install commands, a 7-day roadmap, PRD and prompt templates | Removed | These were the author's working habits rather than verifiable facts, and versions change quickly |
Measurements of how MCP servers behave in practice are in our Korean article on giving agents only bash, and how agent sandboxes isolate work is in The three layers of a coding agent sandbox.
Checklist
- Look at the PORTS column of
docker compose ps. If a line starts with0.0.0.0:, prefix that entry underports:in the Compose file with127.0.0.1:. - Make sure Docker Engine is 28.0.0 or later. In earlier releases, other machines on the same local network segment could reach ports published to localhost.
- Set an API key on Qdrant and a password on Redis. Binding to 127.0.0.1 still lets processes and coding agents on the same machine reach them. Give apps and MCP servers that only query a Qdrant read-only key.
- Do not put passwords in the Compose file or
.env.example. Read them from.envas with${POSTGRES_PASSWORD}, and keep.envout of git. - If you enable Claude Code's sandbox, review localhost access (
allowLocalBinding) and credential-file read access at the same time. The default read scope includes files such as~/.ssh. If the sandbox is mandatory, turn onfailIfUnavailable. - Read a repository's
.mcp.jsonbefore approving it. Automation (claude -p, the Agent SDK) loads it without a prompt, so set the scope with--strict-mcp-configordisabledMcpjsonServers. - Search your Codex config files for
approval_policy = "untrusted"and remove it. The value has been retired, and Codex may fail to start. - Define your own MCP tools with
inputSchemaand state their behavior with annotations. Annotations are optional, but marking query toolsreadOnlyHint: trueand leavingdestructiveHintat its default (true) for tools that delete or modify lets clients such as Codex use them in approval decisions.
What is not settled yet
- This article is a 2026-10-06 rewrite, to current standards, of the initial note first published on 2026-06-16. The June note collected tool recommendations and install commands without sources; this version keeps only defaults that can be confirmed in official docs.
- We did not measure anything ourselves. We did not run the Compose example, connect to the ports from another device, or test localhost access from an agent sandbox. Every behavior is reported as the official docs describe it.
- “Open on every interface” is not the same as actual exposure. What can actually connect depends on your router, corporate network, cloud security groups and host firewall. This article covers only the defaults each project documents.
- The advice to install Ollama as an app on macOS is an inference from comparing the docs. Ollama's docs do not say outright that Docker cannot use the Apple GPU; we only confirmed that its Docker docs give no instructions for running on the Apple GPU.
- We did not check Codex's sandbox read scope or what Codex does when the sandbox cannot start. The empty cells in the table are the ones we did not check.
- Versions and setting keys change quickly. Claude Code ships a new version almost every day, and at the time of checking the Codex docs had moved to
learn.chatgpt.com. Changes after 2026-10-06 are not reflected.
References
All documents were opened and compared against the original text on 2026-10-06. Where possible, we fetched the Markdown source or the documentation source on GitHub.
| Source | What this article took from it | Type |
|---|---|---|
| Docker, Port publishing and mapping | “insecure by default”, binding to 127.0.0.1, the L2 warning for releases before 28.0.0 | Primary |
| Docker, Packet filtering and firewalls | Docker's rules applying before ufw | Primary |
| Docker Desktop, Networking | Default 0.0.0.0 binding for -p, changing the default, filtering by com.docker.backend | Primary |
| Compose file reference, services · Networking in Compose | 0.0.0.0 when host_ip is unset, the 127.0.0.1: format, access by service name | Primary |
| Ollama docs (faq, api/authentication, docker, macos) · Dockerfile | No authentication on the local API, default 127.0.0.1 binding, the image's OLLAMA_HOST=0.0.0.0:11434, GPU support. Latest release v0.35.1 (2026-09-29) | Primary (developer) |
| Qdrant, Security · Quickstart | No authentication by default, binding to all interfaces, API and read-only keys, TLS recommendation, ports 6333 and 6334. Latest release v1.19.2 (2026-10-05) | Primary (developer) |
| Official Redis image docs · Redis security · Official PostgreSQL image docs | Redis protected mode turned off and config-file mounting, requirepass, PostgreSQL's password requirement and the PGDATA change in 18 | Primary |
| Claude Code, MCP · Sandboxing · Changelog | MCP scopes, approval, workspace trust, environment variable substitution; sandbox defaults and the localhost warning; latest version 2.1.290 | Primary (developer) |
| Codex, Agent approvals & security | Sandbox on and network off by default, the retired untrusted policy, allow_local_binding, approval for destructive annotations | Primary (developer) |
| MCP specification 2026-07-28, Tools · schema.ts | The inputSchema field, annotation defaults and trust conditions, the human-in-the-loop recommendation | Primary |
| Claude docs, Tool use overview | The input_schema field in Messages API tool definitions | Primary (developer) |
We rewrote the 2026-06-16 initial note to current standards on 2026-10-06. The first version's tool recommendation table, hardware recommendations and install commands were removed for lack of evidence, and the Compose and MCP tool examples were corrected to match the official docs. If the docs change, we will update this article and note the revision date.