Both of today's tasks declare far less than you would expect, and both have a rule waiting in the part you did not declare. A Docker network is three flags and changes how containers find each other. A DynamoDB table declares one attribute out of three, and the one word the task chose for another is reserved.
One Docker task, one AWS task. Create a user-defined network, then build a DynamoDB table and prove two items have the right status. The tasks come from the KodeKloud Engineer platform.
The reason to create a network is not isolation
docker network ls
docker network create <name> --driver <driver> --subnet <CIDR> --ip-range <CIDR inside it>
docker network inspect <name>
Six drivers ship with Docker, and they are not variations on a theme. bridge is the default, host removes network isolation from the host entirely, none isolates completely, overlay joins multiple daemons for Swarm, and ipvlan and macvlan put containers onto the physical network, the latter making them appear as devices on the host's own network.
But the thing that makes a user-defined network worth creating is smaller and more useful than any of that. Containers on the default bridge cannot refer to each other by name. Containers on a network you created use Docker's embedded DNS server and can reach each other by container name.
That is the whole feature. Every multi-container stack that connects to db rather than 172.18.0.3 is relying on it, including the Compose files coming up later this week, which get a project network for free and never mention DNS anywhere.
Two smaller things worth knowing. --subnet is the CIDR the network occupies, and --ip-range allocates container addresses from a sub-range inside it, which is how you keep the rest of the block for addresses you assign yourself. And macvlan and ipvlan are not drop-in swaps for a bridge: they attach to a physical interface and need the host's network to cooperate.
Finish with inspect, not ls. ls proves a name exists. inspect shows the driver, the addressing and what is attached, which is what the task actually specified.
You declare the keys and nothing else
aws dynamodb create-table --table-name datacenter-tasks \
--attribute-definitions AttributeName=taskId,AttributeType=S \
--key-schema AttributeName=taskId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
The items carry taskId, description and status. Only one of those appears in the command, and that is correct. AWS defines AttributeDefinitions as an array of attributes that describe the key schema for the table and indexes, and nothing beyond that. Everything else comes into existence when an item carries it.
This reliably catches people arriving from SQL, who read --attribute-definitions as a column list and try to declare all three. Adding description there without using it in a key is an error, because DynamoDB has no use for the definition.
One habit before the first write:
aws dynamodb wait table-exists --table-name datacenter-tasks
create-table returns CREATING, and writing to a table in that state fails with ResourceNotFoundException, which reads exactly like a typo in the table name.
Every value carries its type, and numbers are strings
{"taskId":{"S":"1"},"description":{"S":"Learn DynamoDB"},"status":{"S":"completed"}}
S string, N number, B binary, plus BOOL, L, M, NULL and the set types. The one that surprises people is that N values are written as JSON strings: {"N": "42"}, never {"N": 42}. AWS gives the reason directly, which is that numbers are sent across the network as strings to maximise compatibility across languages and libraries. They are still treated as numbers once they arrive.
Note taskId is {"S":"1"}, the character, not the integer. The key schema declared it S, so {"N":"1"} would be rejected outright as a key type mismatch.
The word the task chose is reserved
aws dynamodb scan --table-name datacenter-tasks \
--filter-expression "status = :v" \
--expression-attribute-values '{":v":{"S":"completed"}}'
Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: status
DynamoDB reserves several hundred words in its expression language and they are ordinary English. status, name, size, count, timestamp, year, data, value, source, region, owner, hash and key are all on the published list. Almost any attribute name you would reach for first is on it somewhere.
The fix is an expression attribute name, which is a placeholder:
aws dynamodb scan --table-name datacenter-tasks \
--filter-expression "#s = :v" \
--expression-attribute-names '{"#s":"status"}' \
--expression-attribute-values '{":v":{"S":"completed"}}' \
--query 'Items[].taskId.S' --output text
Two substitution mechanisms, and conflating them is the usual next mistake. #name stands in for an attribute name and its value is a bare string. :value stands in for a value and its value is a typed descriptor. # is what to look at, : is what to compare against.
Worth noticing that put-item needed none of this. Reserved words only matter inside expressions, so an attribute literally called status stores perfectly well and only becomes awkward the moment you query on it. Which points to the real lesson, upstream of all of it: do not name an attribute status. taskStatus costs nothing and deletes the problem from every future query.
One last thing about that verification, because it is doing something the get-item before it did not. get-item shows the item and lets me read the status off the screen. The filtered scan makes DynamoDB assert the status matches and hand back the ID. Same answer, different authority. That said, FilterExpression is applied after the read, so a scan reads the whole table and pays for all of it before discarding what does not match. Fine for two items, a bad habit at a million.
The undeclared part still has rules
The Docker network and the DynamoDB table are both mostly implicit. You do not wire containers to each other, you put them on a network and naming resolves. You do not declare a schema, you write items, and the attributes appear.
In both cases, the implicit part is not lawless. Name resolution only works on a network you created. Undeclared attributes are fine until one of them collides with a reserved word you had no reason to know about.
So here is the Day 42 question. In the system you work on, what behaviour are you relying on that nothing in your configuration actually states?
Day 42 down. Fifty-eight to go.
Top comments (3)
Solid one. The reserved-word trap is cheap to know and expensive to discover live.
The framing you landed on, "the undeclared part still has rules," is the one I'd steal. Same shape shows up a layer up the stack too. I run GPU provisioning across multiple providers, and none of them declare what they actually guarantee. A "ready" instance on one provider means fully warm. On another it means the container just started pulling. Nothing in the API response says which. You only find out by timing it yourself and watching what breaks.
Your DynamoDB example and my provider timing example are basically the same bug. Trusting that the interface's silence means default behavior instead of undocumented behavior. The fix is the same too. Don't infer, measure it directly and let the real system tell you.
Curious what's next on the list. Are you planning to hit ECS/Fargate or stay EC2 and managed services for the rest of the 100?
Both traps today are ones I've paid for. Writing to a table still in CREATING and getting ResourceNotFoundException really does read exactly like a typo in the name — the wait table-exists habit is cheap insurance, and I've since made it part of every bootstrap script.
The attribute-definitions confusion is the classic SQL migration bug: it is a key schema description, not a column list, and declaring an unused attribute fails. The day the S / N type tags on every value clicked for me was the day I stopped thinking of DynamoDB as schemaless — there is a schema, it just moved to write time.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.