Synchronizing Timezones between Java Application and Docker Container
I made a Spring Boot application that uses the java.time
class a lot. Time-related classes are used, everywhere: from persistence layer to the HTML templates.
A problem that I saw early on, is that the widget that shows the "Today's Report" shows the report for yesterday. Since I did not specify the timezone, it used the UTC timezone and made "today" 8 hours late. The that has been implemented is to control the timezone by creating a Clock
bean.
@Configuration
public class ClockConfiguration {
@Bean
public Clock systemClock() {
return Clock.system(ZoneId.of("UTC+8"));
}
}
Then using that bean to get the current time:
@Service
@RequiredArgsConstructor // lombok constructor
public class AggregatorService {
// autowired in the generated constructor
private final Clock systemClock;
public List<DailyAggregate> aggregateDaily() {
var today = LocalDate.now(systemClock);
// service logic ....
}
}
That solved the problem! Now the time is presented with the correct timezone.
However...
I noticed that the timezone used is different when the app is run locally using Gradle compared when running the app inside a Docker container.
The application instance that is run outside a Docker container uses the correct timezone, and the app inside the Docker container uses UTC.
Setting Timezone Data in Docker Container
The goal here is to add timezone data to the Docker container.
One of the simpliest way is to add a TZ
environment variable to the Dockerfile:
ENV TZ="Asia/Manila"
But that approach is static. It requires knowledge on what the timezone value actually is. What I did in my container is I mounted the host's /etc/timezone
to the container's volumes.
Mounting timezone file to the container
In Docker, you need to use the -v
flag for the docker run
command to mount a volume from the host to the container:
docker run --detach \
--name=springboot-container \
-v "/etc/timezone:/etc/timezone:ro" \
-v "/etc/localtime:/etc/localtime:ro" \
springapp:latest
The usage of -v
flags above goes like this:
Since the /etc/timezone
and /etc/localtime
are system files, the path should be as is. Additionally, since the container needs to look at the file, not modify it, the volumes should be mounted as read-only.
Mounting using docker-compose
Since the project is already using Docker Compose, it would be better to set the volume mounting in the docker-compose.yml
file rather than the docker run
command. This makes the volume mounting command set in a file, and probably saved to your version control.
This is done by listing the time and timezone volumes in the volume
list in your service in the docker-compose.yml
.
version: "3.3"
services:
web_app:
image: "springapp:latest"
container_name: "springboot-container"
# other web_app service configs here
volumes:
- "/etc/timezone:/etc/timezone:ro"
- "/etc/localtime:/etc/localtime:ro"
# other volumes to mount
And that's it! When you docker compose up web_app
the time and timezone from the host will be synced with the Spring Boot app in the "springboot-container"
container.
This is the approach I did to synchronize timezone in my Docker container.