Dockerfile
Below is my Banking Server backend Dockerfile. Here, I’m going to explain each steps.
# Build stage
FROM golang:1.18.3-alpine3.16 AS builder
WORKDIR /app
COPY ../.. .
RUN go build -o main main.go
RUN apk add curl
RUN curl -L https://github.com/golang-migrate/migrate/releases/download/v4.15.2/migrate.linux-amd64.tar.gz | tar xvz
# Run stage
FROM alpine:3.16
WORKDIR /app
COPY --from=builder /app/main .
COPY --from=builder /app/migrate ./migrate
RUN apk add --no-cache bash
COPY app.env .
COPY start.sh .
COPY wait-for-it.sh .
COPY db/migration ./migration
EXPOSE 8080
CMD ["/app/main"]
ENTRYPOINT ["/app/start.sh"]
Lets start from the beginning!
Build Stage
FROM golang:1.18.3-alpine3.16 AS builderFirst, alpine is small version of linux. They dont have bash, curl or other libraries. So for me, I need only golang environment and basic linux. Thus this compact version of linux fits to me.WORKDIR /app,COPY . .Set my local environment in/app.RUN go build -o main main.gorungo build -o main main.goin docker container terminal. This command help me to get a compiled version of excutable file:mainRUN apk add curlAs I said before, in alpine version, there is no curl inside. So we need to install curl for download from github.RUN curl -L https://github.com/golang-migrate/migrate/releases/download/v4.15.2/migrate.linux-amd64.tar.gz | tar xvzI’m going to install golang-migrate to usemigratecommand tools for migration. So install and decompress files. the-Loption means that redirect to location.
The purpose of this build stage is that literally building basic environments. Now its Run Stage.
Run Stage
FROM alpine:3.16WORKDIR /appBasic Linux and Setting up.COPY --from=builder /app/main .In build stage, we will copy executable file main into/appCOPY --from=builder /app/migrate ./migrateAlso we will copy migrates(.exe) which is generated fromcurl -L github.~~~.RUN apk add --no-cache bashalpine image doesn’t have bash, so I will install that. I will use bash for run shell scripts.COPY app.env .app.envcontains server configuration like below. From this environment files, I use Viper library to automatically set configuration on server. But, only use Viper as local testing.
DB_DRIVER=postgres
DB_SOURCE=postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable
SERVER_ADDRESS=0.0.0.0:8080
ACCESS_TOKEN_DURATION=15m
TOKEN_SYMMETRIC_KEY=12345678901234567890123456789012
COPY start.sh .I usestart.shto run db migration to my database and start the app.
#!/bin/sh
set -e
echo "run db migration"
/app/migrate -path /app/migration -database "$DB_SOURCE" -verbose up
echo "start the app"
exec "$@"
COPY wait-for-it.sh .As I use two containers, Postgres and Backend-Service, my service needs to wait until Postgres container is ready. This shell script listen port 5432, so can decide whethere this port is ready. This file is long so you can check my github.COPY db/migration ./migrationstart.shgenerate SQL queries for migration indb/migration, and I will copy these schemes into/migrationEXPOSE 8080I will expose this service locally with port8080.CMD ["/app/main"]Set/app/mainas a default command.ENTRYPOINT [“/app/start.sh”] When this Dockerfile build & run in container,
/app/start.shwill be executed first.
docker-compose
- Docker compose simplifies management by automatically building and executing services from multiple containers. Below is my setting for composing docker containers.
like kubernetes
services:
postgres:
image: postgres:12-alpine
environment:
- POSTGRES_USER=root
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=simple_bank
ports:
- "5432:5432" # purpose to exposeing ports. Except this ports, you can use only inside services
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- DB_SOURCE=postgresql://root:secret@postgres:5432/simple_bank?sslmode=disable
depends_on:
- postgres
entrypoint: ["/app/wait-for-it.sh","postgres:5432","--","/app/start.sh"]
command: ["/app/main"]
services:Declare that we are going to setting containers.postgres:This service name & container name.image: postgres:12-alpineWe now get image of postgres from dockerhub.environment: - POSTGRES_USER=root - POSTGRES_PASSWORD=secret - POSTGRES_DB=simple_bankSetting user, password, db name.ports:Just for here, we will expose port outside.api,build,context: .,dockerfile: DockerfileWe will build image with Dockerfile in this folder.ports: - "8080:8080"Normally, backend server’s port doesn’t need to show their ports outside.
Actually, It is critical when you expose Postgres ports outside. Because malicious users can access to server’s DB and take out all informations. Also it is very vulnerable to DoS/DDoS attack(especially low-rate DoS Attack).
environment: - DB_SOURCE=~~~Setting Env. Instead ofsecret@localhost, set assecret@postgresso postgres service’s network will be fitted. Generate services using docker compose will seperatepostgresservice andapiservice as a different IP address likepostgres=’172.16.0.2’,api=’172.16.0.3’. Sosecret@localhostmeanssecret@172.16.0.3. But it should besecret@172.16.0.2. So rather setsecret@172.16.0.2, you can just set a service name there.depends_on: - postgresapi service will need postgres service. So After postgres service is running, api service will run.entrypoint: ["/app/wait-for-it.sh","postgres:5432","--","/app/start.sh"]First, run/app/wait-for-it.sh, so set listening port for postgres server. Second, setpostgres:5432as a argument ofwait-for-it.sh. Lastly, all things ready, startstart.sh
Before
gyuminhwangbo@Gyuminui-MacBookPro simplebank % docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
After
gyuminhwangbo@Gyuminui-MacBookPro simplebank % docker compose up
...
gyuminhwangbo@Gyuminui-MacBookPro simplebank % docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
simplebank_api latest 7da6148aa0f0 3 weeks ago 52.1MB
gyuminhwangbo@Gyuminui-MacBookPro simplebank % docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
de0dfecaf757 simplebank_api "/app/wait-for-it.sh…" 47 seconds ago Up 45 seconds 0.0.0.0:8080->8080/tcp simplebank-api-1
5a9c72502355 postgres:12-alpine "docker-entrypoint.s…" 47 seconds ago Up 45 seconds 0.0.0.0:5432->5432/tcp simplebank-postgres-1
gyuminhwangbo@Gyuminui-MacBookPro simplebank % docker network inspect simplebank_default
[
{
"Name": "simplebank_default",
"Id": "8e834d593f880a08051e480628d94b0c61c91964a0e7d76c1e63ae249140193f",
"Created": "2022-07-23T08:03:57.59455131Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "192.168.96.0/20",
"Gateway": "192.168.96.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"5a9c72502355f346543b02b0ba1a89563333f2c3d5450da25e68f6c8c32a14f9": {
"Name": "simplebank-postgres-1",
"EndpointID": "08fdd10da2e5d6ba2b5b9b7b69aebab0797c1e5fd2c24ce5a9a0f2be96aaa4e1",
"MacAddress": "02:42:c0:a8:60:02",
"IPv4Address": "192.168.96.2/20",
"IPv6Address": ""
},
"de0dfecaf75713dbc30595e21cf978472919d6bce9101acba36ab49708fa53cd": {
"Name": "simplebank-api-1",
"EndpointID": "b969560e4d29bcf7cbfbbaf67d524215704919ae5720ce40befbbe7ebc19fb1a",
"MacAddress": "02:42:c0:a8:60:03",
"IPv4Address": "192.168.96.3/20",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {
"com.docker.compose.network": "default",
"com.docker.compose.project": "simplebank",
"com.docker.compose.version": "2.4.1"
}
}
]
By sending HTTP request using Postman, results are below.
Login
Register
ghkdqhrbals
Register again with
ghkdqhrbalsand here is violations!
Register
Kim
Login
Kim
Both
ghkdqhrbals,Kim’s register informations are successfully stored in postgres database.