Container Environment
Understand how Galaxy runs your app. Learn about the PORT variable, graceful shutdowns, environment variables, and network configuration.
Galaxy runs your app inside Docker containers on 64-bit Linux machines in the UTC timezone. Most apps deploy without special configuration, but understanding this environment helps you build apps that scale gracefully.
Works Out of the Box
Most apps run fine without any special setup. This guide explains what's happening under the hood when you need to dig deeper.
The PORT Variable
Here's the most important thing: Galaxy tells your app which port to listen on via the PORT environment variable. Your app must read this variable. Don't hardcode port 3000, 5000, or any number. Galaxy assigns ports dynamically.
// Node.js / Meteor
const port = process.env.PORT || 3000;
app.listen(port);# Python
import os
port = int(os.getenv('PORT', 5000))
app.run(port=port)If your app doesn't listen on the correct port, Galaxy can't route traffic to it. Health checks fail, and your container gets marked unhealthy.
Don't Hardcode Ports
This is the number one deployment issue. Always read the PORT environment variable, Galaxy assigns ports dynamically and your app must respect that.
ROOT_URL
For Meteor apps, ROOT_URL is your app's base URL. Meteor reads it to power Meteor.absoluteUrl(), which generates full URLs for things like email links, OAuth callbacks, and password reset flows.
You define ROOT_URL in the galaxy.meteor.com.env section of your settings.json, and Galaxy extracts it as an environment variable at deploy time. The value should include the protocol and your hostname, like https://myapp.sandbox.galaxycloud.app or https://yourdomain.com if you've added a custom domain.
When you create a new Meteor app through the Push to Deploy wizard and you don't provide a settings.json file, Galaxy pre-fills ROOT_URL from the subdomain you choose so you don't have to type it. From then on, the value lives in your settings, and keeping it in sync with your active hostname is up to you.
CLI deploys (meteor deploy --settings settings.json) and Repository Mode follow the same rule: whatever's in your settings file is what your container gets.
Switching to a Custom Domain?
Galaxy doesn't recalculate ROOT_URL when you add or change a domain. Update the value in your Meteor settings so Meteor.absoluteUrl() returns the right hostname.
Graceful Shutdown
When Galaxy needs to stop your container (new deployment, scaling down, infrastructure maintenance), it doesn't just kill your app. You get time to clean up.
Here's the sequence:
- Galaxy sends
SIGTERMto your container - Your app has a grace period to finish what it's doing (5 seconds by default, configurable up to 600)
- After the grace period, Galaxy sends
SIGKILLand the container dies
The grace period is your chance to close database connections, finish processing requests, and notify connected clients.
Handling SIGTERM
Catch the signal in your app to do cleanup:
process.on('SIGTERM', () => {
console.log('Received SIGTERM, shutting down gracefully...');
// Close server to stop accepting new connections
server.close(() => {
console.log('Server closed');
process.exit(0);
});
// Force exit after grace period if cleanup takes too long
setTimeout(() => {
console.log('Forcing exit');
process.exit(1);
}, 25000); // Leave some buffer before SIGKILL
});Gradual Client Transitions for Meteor
Running a Meteor app with many connected users? You want to migrate them gracefully to new containers during deploys. The @meteorjs/ddp-graceful-shutdown package handles this automatically:
meteor npm install @meteorjs/ddp-graceful-shutdownimport { DDPGracefulShutdown } from '@meteorjs/ddp-graceful-shutdown';
Meteor.startup(() => {
DDPGracefulShutdown.configure({
// Gradually close connections over the grace period
});
});This spreads client reconnections over time instead of forcing everyone to reconnect at once.
Adjusting the Grace Period
The default 5-second grace period works for most apps. If your app needs more time (heavy background processing, long-running requests), adjust it in your app's Settings > General tab in the Galaxy dashboard. You can set it anywhere from 5 to 600 seconds.
Galaxy tells your app the grace period via the METEOR_SIGTERM_GRACE_PERIOD_SECONDS environment variable.
Plan for Shutdown
Design your app to handle shutdown gracefully. Don't start long-running tasks that can't be interrupted, or use job queues that persist work across restarts.
Environment Variables
Galaxy automatically sets several environment variables. Some you'll use directly, others work behind the scenes.
Variables You'll Use
| Variable | Example | Purpose |
|---|---|---|
PORT | 3000 | Port your app must listen on |
ROOT_URL | https://myapp.sandbox.galaxycloud.app | Your app's base URL for generating links |
METEOR_SETTINGS | {"public": {...}} | Your settings.json contents as Meteor.settings |
METEOR_SIGTERM_GRACE_PERIOD_SECONDS | 5 | How long your app has to shut down gracefully |
Variables Galaxy Manages
| Variable | Example | Purpose |
|---|---|---|
GALAXY_APP_ID | Ss8o4Y6KrvFBKKkqM | Internal identifier for your app |
GALAXY_APP_VERSION_ID | 123 | The container's app version number |
GALAXY_CONTAINER_ID | Ss8o4Y6KrvFBKKkqM-3n28 | Unique identifier for this container |
APM_* | Various | Configuration for Monti APM (Professional plan) |
What You Can Override
Add your own environment variables or override most Galaxy-set variables through your settings.json or the Galaxy dashboard.
Cannot override: GALAXY_CONTAINER_ID, KADIRA_OPTIONS_HOSTNAME, and GALAXY_LOGGER (these are container-specific).
Memory Management
Modern Node.js (v12+) is container-aware. It automatically detects your container's memory limits and sets the heap to about 50% of the container size, up to a maximum of 2GB. For most apps, this works without any configuration.
If you're experiencing out-of-memory crashes, traffic spikes overwhelming your containers, or need more than the 2GB default heap cap, you can tune memory allocation manually using GALAXY_NODE_OPTIONS (for Meteor apps) or NODE_OPTIONS (for Web Apps).
For detailed recommendations on heap size settings for each container type, see the Memory Management guide.
Network Environment
Incoming Connections
Galaxy runs your containers in a firewalled network. Only one port is exposed: the one Galaxy assigns via $PORT.
Galaxy handles the routing: HTTP connections (port 80) route to your container, HTTPS connections (port 443) route to your container if you've configured SSL. You cannot serve connections on any other ports.
All external traffic comes through Galaxy's load balancer. That means your app sees requests coming from the proxy, which affects how the client IP shows up in your code. The next section covers how to read the real address.
Reading the Real Client IP
Trying to capture user IPs in your app and seeing weird addresses like 10.42.x.x instead of real public IPs? That's the address of your container, not your user. By default, Meteor treats HTTP_FORWARDED_COUNT as 0 and returns whatever IP it sees as the immediate connection peer. For Galaxy apps, that peer is the load balancer, not the original client. To get real client IPs, you have to set the variable yourself.
Set HTTP_FORWARDED_COUNT
Add HTTP_FORWARDED_COUNT to the galaxy.meteor.com.env section of your settings.json. The value should match the total number of proxies between your user and your container.
For Galaxy by itself, HTTP_FORWARDED_COUNT=1 covers the load balancer hop and is the right starting point. Add Cloudflare with proxying enabled, Fastly, or another reverse proxy in front of Galaxy, and the count goes up by one for each layer (HTTP_FORWARDED_COUNT=2 with Cloudflare on top of Galaxy, and so on).
For details on how Meteor uses this value to pick an address from X-Forwarded-For, see the Meteor environment variables docs.
Not sure how many hops you have? Set it to 1, deploy, log what this.connection.clientAddress returns, and bump the count up until you get real public IPs.
Read X-Forwarded-For Directly
If HTTP_FORWARDED_COUNT doesn't fit your situation (proxy counts that vary by route, custom header logic, anything beyond a simple chain), you can parse the header yourself. Galaxy forwards X-Forwarded-For through to your container, so the value is there to read.
function getClientIpXForwarded() {
const conn = this.connection;
if (conn && conn.httpHeaders) {
const xff = conn.httpHeaders["x-forwarded-for"] || conn.httpHeaders["X-Forwarded-For"];
if (xff) return xff;
}
}Use this inside a Meteor method (or anywhere this.connection is available) to grab the raw header. The value is a comma-separated list shaped like client, proxy1, proxy2. The leftmost entry is the original client, the rightmost is the proxy closest to your app. Pick the position that matches the part of the chain you trust.
Don't Trust Untrusted Hops
Any client can send X-Forwarded-For in their request, including a malicious user spoofing whatever address they want. Only trust the portion of the chain added by proxies you actually control.
Outgoing Connections and IP Whitelisting
When your app connects to external services (databases, APIs, third-party services), those connections appear to come from fixed IP addresses. These aren't the actual machine IPs, they're Galaxy's outgoing proxy addresses.
This matters for IP whitelisting. Many database providers require you to whitelist the IP addresses that can connect. If you're using MongoDB Atlas, AWS RDS, or similar services, you'll need to whitelist Galaxy's outgoing IPs.
To find your outgoing IPs:
- Go to your app's Settings page in the Galaxy dashboard
- Look for IP addresses listed under outgoing connections
- Add these addresses to your database or service's whitelist
Professional Plan Required (Meteor Apps Only)
IP whitelisting information is available for Meteor apps on the Professional plan. Upgrade if you need to access these IP addresses.
CIDR notation: If your service wants IP addresses in CIDR format, add /32 to each IP. For example, 203.0.113.42 becomes 203.0.113.42/32.
Shared IP Addresses
Whitelisted IP addresses are shared among all Galaxy Professional customers. Whitelisting adds a security layer but shouldn't be your only protection. Always use strong authentication for databases and services.
IPv6 Support
Galaxy supports IPv6 for apps on paid plans. Upgrade it in your app's Plans page, then configure DNS at your domain registrar.
IPv6 helps future-proof your app as IPv4 addresses become scarcer.
IPv6 Availability
IPv6 is automatically enabled for all paid plans. Free plans only have IPv4 available on the endpoint.
Health Checking
Galaxy monitors your app's health by making HTTP requests. Understanding this helps you deploy reliably.
How Health Checks Work
Galaxy expects your app to:
- Listen on the port from
$PORT - Respond to
GET /requests - Return valid HTTP within 5 seconds
Health check requests include a User-Agent header containing Galaxybot/.
Galaxy currently just checks that the response is valid HTTP. But returning a 5xx status code is a bad idea, as Galaxy may refine what "healthy" means in the future.
What Happens When Containers Are Unhealthy
If your container fails health checks:
- New containers get 10 minutes to become healthy before replacement
- Previously healthy containers get 5 minutes to recover before replacement
- Unhealthy containers don't receive new client connections
- Galaxy logs unhealthy events and can send notifications
You can disable automatic unhealthy container replacement in your app's Settings page. Useful if your app legitimately runs heavy processes that temporarily block health checks.
Create a Health Endpoint
While Galaxy checks /, consider creating a dedicated /health endpoint that verifies your app's critical dependencies (database connection, external services) and returns meaningful status information.
Load Balancing
Galaxy distributes incoming connections across your healthy containers using a least loaded algorithm (the container with the fewest existing connections gets new requests).
How Load Balancing Works
- New connections go to the least loaded healthy container
- Existing connections stick to their current container (no active rebalancing)
- If a container becomes unhealthy, its users get routed to healthy containers
- High CPU or memory usage doesn't trigger rebalancing (only unhealthy status does)
If one container gets into trouble but stays "healthy" (responds to health checks), its connected users won't automatically move to other containers.
Deploy Behavior
When you deploy a new version:
- Galaxy waits for all new containers to become healthy (at least 10 minutes, longer for many containers)
- If new containers don't become healthy, Galaxy rolls back to the previous version
- Connected users transition gradually if you've configured graceful shutdown

