
Data transfer fees are one of the more annoying line items in a cloud bill. When two services talk to each other over the public internet, you pay for every byte. Google Cloud Run's multi-container support changes this for sidecar-style architectures: containers in the same Cloud Run application share a network namespace and communicate over localhost, so none of that traffic counts as external data transfer.
Understanding Google Cloud Run's Multi-Container Support
Cloud Run has always been straightforward for single-container deployments. Multi-container support adds the ability to run sidecar containers alongside the main one, all sharing the same environment. The key detail: because they share a network namespace, inter-container requests never leave the host. That eliminates the data transfer cost and reduces latency compared to calling a separate service.
Benefits of Multi-Container Applications in Cloud Run
- Cost reduction: inter-service traffic over
localhostdoesn't incur internet data transfer fees. - Simplified architecture: auxiliary services like logging agents or language-specific processors run alongside the main container without a separate deployment.
- Reduced attack surface: fewer external network calls.
- Lower latency for inter-process communication.
Reducing Data Transfer Costs
In a standard microservice setup, each service call crosses the network, incurring latency and transfer costs. Multi-container deployments eliminate both for traffic between containers in the same application.
How Multi-Container Deployments Reduce Costs
Containers share the same localhost interface. Traffic between them never traverses the public internet, so it doesn't appear in your data transfer bill. Shared resources also mean lower memory and CPU overhead compared to running separate Cloud Run services.
Building a Multi-Container Application: Node.js and Python Example
The scenario: a Node.js container handles HTTP requests from users and calls a Python container for data processing. Because they're co-located, that internal call costs nothing in transfer fees.
Application Overview
The Node.js container serves the web interface and handles user requests. The Python container performs data processing, such as machine learning inferences or data analysis.
Setting Up the Node.js Container
// server.js
const express = require('express');
const axios = require('axios');
const app = express();
const PORT = process.env.PORT || 8080;
app.get('/', async (req, res) => {
try {
// Call the Python service
const response = await axios.get('http://localhost:5000/process');
res.send(`Python Service Response: ${response.data}`);
} catch (error) {
res.status(500).send('Error communicating with Python service');
}
});
app.listen(PORT, () => {
console.log(`Node.js server listening on port ${PORT}`);
});
Dockerfile for Node.js Container:
# Node.js Dockerfile
FROM node:14
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
COPY package*.json ./
RUN npm install
# Bundle app source
COPY . .
# Expose the port
EXPOSE 8080
# Run the application
CMD [ "node", "server.js" ]
package.json:
{
"name": "nodejs-container",
"version": "1.0.0",
"description": "Node.js container for multi-container Cloud Run application",
"main": "server.js",
"dependencies": {
"axios": "^0.21.1",
"express": "^4.17.1"
}
}
Setting Up the Python Container
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/process')
def process():
# Perform data processing
return 'Data processed successfully by Python service'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Dockerfile for Python Container:
# Python Dockerfile
FROM python:3.8-slim
# Install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Copy the application
COPY . /app
WORKDIR /app
# Expose the port
EXPOSE 5000
# Run the application
CMD [ "python", "app.py" ]
requirements.txt:
flask
Configuring the Cloud Run Service
Define a service.yaml file that lists both containers:
# service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: multi-container-app
spec:
template:
spec:
containers:
- image: gcr.io/your-project-id/nodejs-container
ports:
- containerPort: 8080
- image: gcr.io/your-project-id/python-container
ports:
- containerPort: 5000
Replace your-project-id with your actual Google Cloud project ID. Both containers share the same network namespace, so the Node.js server can reach the Python service at http://localhost:5000.
Deploying to Google Cloud Run
1. Build and Push the Containers
Build and push the Node.js container:
docker build -t gcr.io/your-project-id/nodejs-container .
docker push gcr.io/your-project-id/nodejs-container
Build and push the Python container:
docker build -t gcr.io/your-project-id/python-container .
docker push gcr.io/your-project-id/python-container
2. Deploy the Service
gcloud run services replace service.yaml
This command creates or replaces the Cloud Run service defined in service.yaml.
Verifying the Application
After deployment, open the Cloud Run service URL. The Node.js application calls the Python container internally and returns:
Python Service Response: Data processed successfully by Python service
Advantages of This Approach
- Lower latency between containers compared to calling a separate Cloud Run service over the internet.
- No external data transfer charges for the inter-container communication.
- One deployment unit to manage instead of two separate services.
Use Cases for Multi-Container Applications
Multi-container deployments on Cloud Run fit well for sidecar patterns (logging agents, authentication proxies, monitoring collectors), mixed-language processing (a Python ML model alongside a Node.js API), and gradual decomposition of a monolith where you want co-location without full microservice overhead.
Best Practices
- Set resource limits for each container to prevent one from starving the other.
- Implement health checks on both containers so Cloud Run can detect failures accurately.
- Use a shared logging format so logs from both containers appear coherently in Cloud Logging.
Potential Challenges
Multi-container configurations add deployment complexity. Container startup order isn't guaranteed, so the Node.js server needs to handle the case where the Python service isn't ready yet. Port assignments must not conflict. And if both containers need the same environment variable with different values, you'll need to be explicit in the service.yaml.
Conclusion
Multi-container Cloud Run removes the transfer cost for services that would otherwise call each other over the network. The Node.js plus Python example above is straightforward to deploy and immediately eliminates inter-service data transfer charges. For architectures where two services communicate frequently, this is a real savings, and the shared-network model scales to more complex sidecar patterns without changing how the containers talk to each other.