Firewall
Section titled “Firewall”This page covers packet-level control of container traffic: what can connect in (ingress) and what the container can reach out to (egress).
API endpoints summary
Section titled “API endpoints summary”Complete endpoint documentation:
- GET /api/v1/containers/{id}/firewall/rules - List all firewall rules
- POST /api/v1/containers/{id}/firewall/ingress - Add ingress rule
- POST /api/v1/containers/{id}/firewall/egress - Add egress rule
- DELETE /api/v1/containers/{id}/firewall/ingress - Remove ingress rule(s)
- DELETE /api/v1/containers/{id}/firewall/egress - Remove egress rule(s)
- PATCH /api/v1/containers/{id}/firewall/ingress - Toggle ingress rule state
- PATCH /api/v1/containers/{id}/firewall/egress - Toggle egress rule state
- POST /api/v1/containers/{id}/firewall/reset - Reset firewall
Enforcement, directions, and actions
Section titled “Enforcement, directions, and actions”The firewall runs on your server’s host kernel, completely outside the containers.
What that means:
- Containers cannot bypass firewall rules
- Containers cannot modify their own firewall
- Rules survive container restarts and snapshots
Two directions:
- Ingress - traffic coming into the container (who can connect)
- Egress - traffic going out from the container (what the container can reach)
Three actions:
- allow - permit matching traffic
- reject - block and notify (ICMP/TCP unreachable)
- drop - silently ignore (appears offline to scanners)
Three protocols: tcp, udp, icmp4
Examples
Section titled “Examples”Allow SSH from a specific IP
Section titled “Allow SSH from a specific IP”# Add an ingress rulehoody firewall ingress create -c $CONTAINER_ID --action allow --protocol tcp --description "Allow SSH from office" --destination-port 22 --source 203.0.113.50/32await client.api.firewall.addIngressRule(CONTAINER_ID, { action: 'allow', protocol: 'tcp', description: 'Allow SSH from office', destination_port: '22', source: '203.0.113.50/32',});curl -X POST "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/firewall/ingress" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "allow", "protocol": "tcp", "description": "Allow SSH from office", "destination_port": "22", "source": "203.0.113.50/32" }'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Adds the ingress rule as a single GET link, ready to paste into a browser.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/firewall/ingress&method=POST&bearer_token=TOKEN&json={"action":"allow","protocol":"tcp","description":"Allow%20SSH%20from%20office","destination_port":"22","source":"203.0.113.50/32"}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Block all outbound TCP traffic
Section titled “Block all outbound TCP traffic”# Add an egress rulehoody firewall egress create -c $CONTAINER_ID --action drop --protocol tcp --description "Block all TCP egress" --destination 0.0.0.0/0 --destination-port 1-65535await client.api.firewall.addEgressRule(CONTAINER_ID, { action: 'drop', protocol: 'tcp', description: 'Block all TCP egress', destination: '0.0.0.0/0', destination_port: '1-65535',});curl -X POST "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/firewall/egress" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "drop", "protocol": "tcp", "description": "Block all TCP egress", "destination": "0.0.0.0/0", "destination_port": "1-65535" }'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Adds the egress-drop rule as a single GET link. Route it through a different running container’s curl-1 (OTHER_CONTAINER_ID below): this rule cuts CONTAINER_ID’s own outbound TCP, including the request applying it. This blocks TCP only — pair it with the UDP and ICMP versions below for a fully offline container.
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/firewall/egress&method=POST&bearer_token=TOKEN&json={"action":"drop","protocol":"tcp","description":"Block%20all%20TCP%20egress","destination":"0.0.0.0/0","destination_port":"1-65535"}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Use case: switching a container’s outbound traffic off entirely.
Allow one service only
Section titled “Allow one service only”Allow only HTTPS to a specific API:
# Add an egress rulehoody firewall egress create -c $CONTAINER_ID --action allow --protocol tcp --description "Allow HTTPS to API" --destination-port 443 --destination 198.51.100.0/24await client.api.firewall.addEgressRule(CONTAINER_ID, { action: 'allow', protocol: 'tcp', description: 'Allow HTTPS to API', destination_port: '443', destination: '198.51.100.0/24',});curl -X POST "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/firewall/egress" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "allow", "protocol": "tcp", "description": "Allow HTTPS to API", "destination_port": "443", "destination": "198.51.100.0/24" }'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Adds the egress-allow rule as a single GET link, scoped to HTTPS traffic toward the given CIDR.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/firewall/egress&method=POST&bearer_token=TOKEN&json={"action":"allow","protocol":"tcp","description":"Allow%20HTTPS%20to%20API","destination_port":"443","destination":"198.51.100.0/24"}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Port specification
Section titled “Port specification”{"destination_port": "80"}{"destination_port": "8000-9000"}{"destination_port": "80,443,8080"}{"destination_port": "22,80-90,443,8000-9000"}CIDR notation:
/32- a single IP (203.0.113.50/32)/24- 256 IPs (203.0.113.0/24)/0- all IPs (0.0.0.0/0)
Rule evaluation order
Section titled “Rule evaluation order”First match wins, so create specific rules before broad ones.
Correct order, specific first:
Wrong order, broad first: if you create the drop rule first, the allow rule is never reached.
Common patterns
Section titled “Common patterns”Restrict access to a database container
Section titled “Restrict access to a database container”Allow PostgreSQL from backend only:
Drop all other attempts:
Serve the web publicly and restrict SSH
Section titled “Serve the web publicly and restrict SSH”Allow HTTP/HTTPS from anywhere:
Allow SSH from office only:
Drop other SSH attempts:
Take a container fully offline
Section titled “Take a container fully offline”Block all egress:
Rules match per protocol, so all three (tcp, udp, icmp4) are required. Otherwise ICMP egress stays open under the default-allow policy.
Use this for agent runs you want kept fully offline until you say otherwise.
Allowlist only the destinations you need
Section titled “Allowlist only the destinations you need”Give your programs exactly the destinations they need. Nothing else gets out.
Allow only what your app needs (e.g., your API):
Allow DNS:
Block everything else. Firewall rules are per-protocol, so a catch-all drop is needed for each of tcp, udp, and icmp4:
Traffic then leaves only toward the destinations you chose.
Firewall vs Hoody Proxy
Section titled “Firewall vs Hoody Proxy”The two are separate security layers:
| Layer | Controls | Use For |
|---|---|---|
| Firewall | Network packets (TCP/UDP/ICMP) | Port restrictions, IP filtering |
| Proxy Permissions | HTTP service access | User auth, service-level control |
The firewall controls network traffic; Proxy Permissions controls HTTP service access. Use both together to cover packets and HTTP requests.
See: Proxy Permissions →
Manage rules
Section titled “Manage rules”List rules
Section titled “List rules”# List all firewall rules for a containerhoody firewall list -c $CONTAINER_IDconst { data } = await client.api.firewall.list(CONTAINER_ID);console.log(data);curl "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/firewall/rules" \ -H "Authorization: Bearer $TOKEN"One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Lists the container’s firewall rules as a single GET link.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/firewall/rules&method=GET&bearer_token=TOKEN&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Disable a rule without removing it
Section titled “Disable a rule without removing it”# Toggle an ingress rule's state to disabledhoody firewall ingress toggle -c $CONTAINER_ID --state disabled --description "Allow SSH from office"await client.api.firewall.toggleIngressRule(CONTAINER_ID, { state: 'disabled', description: 'Allow SSH from office',});curl -X PATCH "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/firewall/ingress" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"state": "disabled", "description": "Allow SSH from office"}'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Toggles the matching ingress rule to disabled as a single GET link, without deleting it.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/firewall/ingress&method=PATCH&bearer_token=TOKEN&json={"state":"disabled","description":"Allow%20SSH%20from%20office"}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Remove rules
Section titled “Remove rules”Remove a specific rule:
# Remove a specific ingress rulehoody firewall ingress delete -c $CONTAINER_ID --description "Allow SSH from office" --yesawait client.api.firewall.removeIngressRule(CONTAINER_ID, { description: 'Allow SSH from office',});curl -X DELETE "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/firewall/ingress" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"description": "Allow SSH from office"}'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Removes the matching ingress rule as a single GET link.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/firewall/ingress&method=DELETE&bearer_token=TOKEN&json={"description":"Allow%20SSH%20from%20office"}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Reset completely, removing all rules:
# Reset firewall to default (removes ALL rules)hoody firewall reset -c $CONTAINER_ID --yesawait client.api.firewall.reset(CONTAINER_ID);curl -X POST "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/firewall/reset" \ -H "Authorization: Bearer $TOKEN"One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Removes every rule and resets the firewall to default as a single GET link.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/firewall/reset&method=POST&bearer_token=TOKEN&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Best practices
Section titled “Best practices”Create specific rules before broad ones
Section titled “Create specific rules before broad ones”Create the specific allow rule first:
Then create the broad drop rule:
Use drop for public services
Section titled “Use drop for public services”"action": "drop"= stealth (appears offline to scanners)"action": "reject"= reveals existence (sends ICMP unreachable)
Allow DNS when restricting egress
Section titled “Allow DNS when restricting egress”When blocking egress, remember to allow DNS:
Snapshot before major changes
Section titled “Snapshot before major changes” See: Snapshots →
Useful questions
Section titled “Useful questions”Does the firewall apply to traffic through the Hoody Proxy?
Section titled “Does the firewall apply to traffic through the Hoody Proxy?”No. Hoody Proxy traffic (service URLs like https://{project}-{container}-terminal-1.{server}.containers.hoody.com) bypasses the firewall. Use Proxy Permissions for HTTP service access control.
Can containers modify their own firewall rules?
Section titled “Can containers modify their own firewall rules?”No. Firewall rules are host-enforced and only modifiable via the Hoody API. Containers cannot see or change them.
How does block mode differ from egress rules?
Section titled “How does block mode differ from egress rules?”Network Configuration block mode blackholes all outbound TCP from the container, including calls to other containers’ service URLs. Firewall egress rules are granular: allow some destinations, block others.
Do firewall rules persist through restarts?
Section titled “Do firewall rules persist through restarts?”Yes. Rules are stored at host level and survive container restarts, pauses, and snapshots.
What if I lock myself out with firewall rules?
Section titled “What if I lock myself out with firewall rules?”Open the hoody-terminal URL, since HTTP through the Hoody Proxy bypasses the firewall. From there or from the API, add an allow rule or reset: POST /api/v1/containers/{id}/firewall/reset
Can firewall rules use domain names?
Section titled “Can firewall rules use domain names?”No. The firewall operates at the IP layer, so use CIDR notation only.
Troubleshooting
Section titled “Troubleshooting”Cannot connect after adding rules
Section titled “Cannot connect after adding rules”Solutions:
- List the rules:
GET /api/v1/containers/{id}/firewall/rules - Disable the blocking rule:
PATCH /api/v1/containers/{id}/firewall/ingress {"state": "disabled", "description": "..."} - Add an allow rule for your IP:
POST /api/v1/containers/{id}/firewall/ingress {"action": "allow", "protocol": "tcp", "description": "Allow my IP", "source": "YOUR_IP/32", "destination_port": "1-65535"} - Reset the firewall:
POST /api/v1/containers/{id}/firewall/reset(removes all rules)
Rules not working
Section titled “Rules not working”Check:
- Rule state: must be
"state": "enabled" - Rule order: specific before broad
- Protocol: a TCP rule will not block UDP traffic
Egress rules block package installation
Section titled “Egress rules block package installation”Allow package repositories:
What’s next
Section titled “What’s next”Complete networking setup:
- Network Configuration → - Route traffic through proxies/VPNs
- VPN Inside a Container → - Run a WireGuard tunnel inside the container
- SSH Access → - Secure shell and SFTP
- IPv4 Management → - Dedicated IPs (coming soon)
Related security:
- Proxy Permissions → - Application-level access control
- Security Principles → - Hoody’s security philosophy
What this page covered:
- The firewall is enforced at host level, and containers cannot bypass it
- Ingress controls inbound traffic, egress controls outbound traffic
- Rules are evaluated in order, and the first match wins
- Three actions: allow, reject, drop
- The firewall is independent from Hoody Proxy Permissions