Atlassian’s documentation for local Forge Containers testing describes two supported ways to run a service locally: using the tunnel.docker configuration in manifest.yml, or setting up the Docker Compose stack manually and then starting forge tunnel.
In both cases, the usual workflow is to run the service inside Docker and inspect its behavior through logs.
That works, but for Java services there is a much more practical way to debug locally with IntelliJ IDEA. Instead of relying only on logs, standard Java breakpoints can be used to inspect the code step by step.
This article shows two debugging approaches:
- running the Java service directly from IntelliJ IDEA in debug mode, while keeping only the Forge proxy sidecar in Docker
- keeping the full service inside Docker, but exposing a remote debug port and attaching IntelliJ IDEA to the JVM
The first approach is the most convenient for everyday local development. The second one is closer to the standard container workflow and is useful when debugging the real containerized runtime.
What Atlassian documents today
The official Forge Containers documentation explains how to test a containerized service locally with forge tunnel. The documented flow is centered around Docker: either define the container tunnel in manifest.yml, or launch the required Docker Compose stack manually and then run forge tunnel.
That setup is useful and aligns well with how Forge Containers work. But when debugging Java services, logs are often not enough. In practice, it is often necessary to inspect request payloads, step through controller code, and stop execution exactly at the point where something goes wrong.
That is where IntelliJ IDEA breakpoints make local development much easier.
Option 1: Run the Java service in IntelliJ IDEA and keep only the sidecar in Docker
This is the most convenient approach for local development.
The idea is simple: instead of running the Java service inside Docker, run it directly from IntelliJ IDEA in debug mode on port 8080. Then run only the Forge proxy sidecar in Docker and point it to the locally running service.
This creates a fast edit-run-debug loop and makes Forge Containers feel much closer to normal Spring Boot development.
Step 1. Start the Java service from IntelliJ IDEA
Run your Spring Boot service locally on port 8080 in Debug mode.
If the official Forge Containers Java sample app is being used, make sure the main method is declared as public. For example, in:
forge-containers-app-local/services/java-spring-server/src/main/java/com/atlassian/container/JavaSpringServerApplication.java
@SpringBootApplication
@EnableScheduling
public class JavaSpringServerApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(JavaSpringServerApplication.class, args);
}
}
Step 2. Update docker-compose.yml
Remove the java-service container from the Docker Compose setup and keep only the proxy sidecar. The important part is to point SERVICE_URL to the local JVM process instead of another Docker service:
services:
# java-service:
# build:
# context: ./services/java-spring-server
# dockerfile: Dockerfile
# image: java-service:latest
# container_name: java-service
# ports:
# - "8080:8080"
# environment:
# - FORGE_EGRESS_PROXY_URL=http://proxy-sidecar:7072
proxy-sidecar:
image: forge-ecr.services.atlassian.com/forge-platform/proxy-sidecar:latest
container_name: containers-proxy-sidecar
environment:
# host.docker.internal resolves to your machine from inside the container
- SERVICE_URL=http://host.docker.internal:8080
- FOP_BASE_URL=https://forge-outbound-proxy.services.atlassian.com
- APP_ID=ari:cloud:ecosystem::app/$APP_ID
- ENV_ID=ari:cloud:ecosystem::environment/$APP_ID/$ENV_ID
- JWKS_URL=https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json
- IS_LOCAL_DEV=true
ports:
- "7071:7071"
- "7072:7072"
# depends_on:
# - java-service
In practice, this means commenting out the java-service section entirely and exposing the sidecar ports, especially 7072.
The key change here is:
SERVICE_URL=http://host.docker.internal:8080
That tells the sidecar to forward requests to the Java service running directly on the host machine.
Step 3. Remove the tunnel config from manifest.yml
When using this manual setup, remove the container tunnel block from the manifest for that service.
For example:
services:
- key: java-service
containers:
- key: java-service
# tunnel:
# docker:
# # Either 'build' or 'image' should be supplied, but not both.
# # If 'build' is supplied, the image will be rebuilt each time the tunnel is started.
# build:
# # 'context' must be an absolute or relative path to the Dockerfile.
# context: ./services/java-spring-server
# dockerfile: Dockerfile
# # Alternatively, specify a pre-built image. Leave commented out if using 'build' above.
# # image: java-service:${TAG}
# ports:
# - "8080:8080"
# environment:
# - CUSTOM_TUNNEL_ENV_VAR=custom_tunnel_env_var
# - OFFLINE_ACCESS_USER_IMPERSONATION_DEMO_ACCOUNT_ID=offline_access_user_impersonation_demo_account_id
tag: ${TAG}
health:
type: http
route:
path: /health
resources:
cpu: "1"
memory: "2Gi"
In other words, the container definition remains, but the tunnel: block is removed or commented out.
Step 4. Start the dev loop
After that, run the dev-loop.sh script with the required environment variables configured.
This script is responsible for starting the local development flow around forge tunnel.
Step 5. Set a breakpoint in IntelliJ IDEA
At this point, a normal breakpoint can be placed in the code.
For example, I set one in:
com/atlassian/container/RealtimeEndpoint.java
on this line:
String channelName = body.path("channelName").asText();
Step 6. Trigger the request from Jira
Then open a Jira issue and load the issue panel, for example jira-issue-panel-container.
When the app invoked the /sign-realtime-token endpoint, IntelliJ stopped exactly on the breakpoint.
That is the main benefit of this setup: instead of reading logs after the request is already finished, you can inspect the request in real time, view variables, and step through the code like in a normal local Java application.
Why this approach is so useful
For local development, this was the most productive option for me because it gives:
• normal IntelliJ breakpoints
• full variable inspection
• step-by-step debugging
• faster development than rebuilding the Java container every time
For day-to-day work, this is the setup I would choose first.
Option 2: Keep the full service in Docker and attach IntelliJ IDEA through Remote Debug
The second approach is closer to the standard Forge Containers workflow.
Here, the Java service still runs inside Docker, but the JVM is started with JDWP enabled, and the debug port is exposed to the host machine. IntelliJ IDEA can then attach to that port using a standard Remote JVM Debug configuration.
This is slower than Option 1, but it is useful when you want to debug the actual containerized runtime rather than a locally launched JVM.
Step 1. Update the Dockerfile
In services/java-spring-server/Dockerfile, expose a debug port and start the JVM with remote debugging enabled:
# Expose the app and debug ports
EXPOSE 8080
EXPOSE 8000
CMD ["java", \
"-XX:InitialRAMPercentage=15", \
"-XX:MaxRAMPercentage=35", \
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000", \
"-jar", "/opt/service/service.jar"]
This keeps the application running normally on port 8080, while also exposing port 8000 for IntelliJ remote debugging.
Step 2. Add the debug port to the tunnel config
In manifest.yml, add the debug port to the tunnel configuration:
services:
- key: java-service
containers:
- key: java-service
tunnel:
docker:
build:
context: ./services/java-spring-server
dockerfile: Dockerfile
ports:
- "8080:8080"
- "8000:8000" # Map the debug port to localhost
Without this extra port mapping, IntelliJ IDEA will not be able to connect to the containerized JVM from localhost.
Step 3. Create a Remote JVM Debug configuration in IntelliJ IDEA
In IntelliJ IDEA, create a new Remote JVM Debug configuration with:
• Host: localhost
• Port: 8000
Step 4. Start the local workflow
The flow here is straightforward:
- run forge tunnel
- wait for the local container to build and start
- start the IntelliJ remote debug configuration
- set breakpoints in your code
- trigger the Forge app locally
With that setup, breakpoints can be hit inside the Spring Boot service while invoking the Forge app through the tunnel.
Which option should you use?
Both approaches work, but they solve slightly different problems.
Option 1: Java in IntelliJ IDEA, only the sidecar in Docker
This is the best choice for everyday local development.
It gives you the fastest feedback loop and feels much closer to standard Spring Boot development. You avoid repeated container rebuilds, and debugging is very comfortable.
Option 2: Full container in Docker with Remote Debug
This is the better choice when you want to stay closer to the real container runtime.
It is useful for validating Docker-specific behavior, startup issues, runtime configuration, or anything that behaves differently once the application is packaged and run inside the container.
Final thoughts
Atlassian’s documentation already gives a good starting point for running Forge Containers locally, but Java developers are not limited to logs-only debugging.
If IntelliJ IDEA is being used, Forge Containers can be debugged much more effectively with normal breakpoints:
• either by running the Java service locally and keeping only the proxy sidecar in Docker
• or by exposing a remote debug port and attaching to the JVM inside the container
Useful links
• Atlassian documentation: https://developer.atlassian.com/platform/forge/containers-reference/test-service-locally/
• Example repository: https://github.com/vzakharchenko/forge-containers-app-debugger
Option 1: IntelliJ IDEA + local Java service
• Example project: https://github.com/vzakharchenko/forge-containers-app-debugger/tree/master/forge-containers-app-local
• Code changes: https://github.com/vzakharchenko/forge-containers-app-debugger/commit/95ce43573a365534ffeda024a19aa8c57d6c7ca3
Option 2: Remote Debug inside Docker
• Example project: https://github.com/vzakharchenko/forge-containers-app-debugger/tree/master/forge-containers-app-remote
• Code changes: https://github.com/vzakharchenko/forge-containers-app-debugger/commit/53f0c0715b93bf32893df6f324197ce82b4a866b


