Deploy Part 2
Now it is time to hook up to our PostgreSQL database in production.
Try it out in Dev
To try to connect to PostgreSQL in dev, we can create a little docker compose file to try things out.
postgres.docker-compose.yaml
networks:
pg:
driver: bridge
external: true
services:
postgres:
container_name: postgres
image: postgres:15
networks:
- pg
env_file:
- .env
ports:
- "5432:5432"
Run the docker compose file with this:
docker compose -f postgres.docker-compose.yaml up -d
Session
For your application to hook up to postgres and sessions to be managed properly, we need to modify our .env, app.conf, and InitDB function from models. Then, you will need to manually create a session table.
For right now in dev, the credentials can just be your_username and your_password. These will obviously change for production.
.env
DATABASE_DRIVER=postgres # sqlite3
DATABASE_URL=postgres://your_username:your_password@localhost:5432/your_username?sslmode=disable
POSTGRES_USER=your_username
POSTGRES_PASSWORD=your_password
app.conf
appname = myapp
httpport = 8080
runmode = prod
sessionon = true
# Enable logging
loglevel = info # Adjust as needed
logfile = /var/log/app.log # Path to log file
# Session configuration
sessionprovider = postgresql
sessionproviderconfig = postgres://your_username:your_password@localhost:5432/your_username?sslmode=disable
sessioncookie = true
sessiongcmaxlifetime = 3600 # Session lifetime in seconds
# Security settings
allowcrossdomain = false # Set to true if you need CORS
main.go
For your session to be stored on postgres, you need to make sure you add this import to main.go.
_ "github.com/beego/beego/v2/server/web/session/postgres"
models/default.go
Make sure to add the pq import so that you can Postgres.
package models
import (
"log"
"os"
"github.com/beego/beego/v2/client/orm"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
)
Then, your InitDB should look like this.
func InitDB() {
// NEW: Configure database from env
driverName := os.Getenv("DATABASE_DRIVER")
dataSource := os.Getenv("DATABASE_URL")
driver := orm.DRSqlite
if driverName == "postgres" {
driver = orm.DRPostgres
} else {
driverName = "sqlite3"
dataSource = "./myapp.db"
}
orm.RegisterDriver(driverName, driver)
orm.RegisterDataBase("default", driverName, dataSource)
// You already have this part.
orm.RegisterModel(
new(ContactModel),
new(User),
)
O = orm.NewOrm()
err := orm.RunSyncdb("default", false, true)
if err != nil {
log.Fatalf("Failed to sync database: %v", err)
}
// NEW: Create session table
if driverName == "postgres" {
_, err = O.Raw(`CREATE TABLE IF NOT EXISTS "session" (
"session_key" CHAR(64) NOT NULL,
"session_data" BYTEA,
"session_expiry" TIMESTAMP NOT NULL,
PRIMARY KEY ("session_key")
);`).Exec()
if err != nil {
fmt.Println("table `session` created")
} else {
fmt.Println("`session` table not created, application may not work")
}
}
}
Test locally with:
docker compose -f postgres.docker-compose.yaml up -d
bee run
Production database
You need to create a Postgres Database. First, create a username and password. Each should be randomly generated, and it is nice if the username is lowercase. Therefore, running a command like this should get you what you need:
openssl rand -hex 20
Here is a script, make a file like create_db.sh and run with sh create_db.sh. You will only need to create a production database once.
touch create_db.sh
echo "create_db.sh" >> .gitignore
File contents of create_db.sh:
# Set these yourself
# It is nice to be lowercase/numeric (no caps), so I'd recommend generating with
# NEW_USER=$(openssl rand -hex 20)
NEW_USER=abcdefd566beb912fa25701cda117894b5e8fd5f
NEW_PASS=abcdef0af13364f51f80a8ee4b0f89011db96382
# Run this to create the database
docker exec -it postgres sh -c "psql -U \$POSTGRES_USER -c 'CREATE DATABASE \"$NEW_USER\"'; \
psql -U \$POSTGRES_USER -d \"$NEW_USER\" -c 'CREATE USER \"$NEW_USER\" WITH PASSWORD '\''$NEW_PASS'\'';'; \
psql -U \$POSTGRES_USER -d \"$NEW_USER\" -c 'GRANT ALL PRIVILEGES ON DATABASE \"$NEW_USER\" TO \"$NEW_USER\";'; \
psql -U \$POSTGRES_USER -d \"$NEW_USER\" -c 'GRANT USAGE, CREATE ON SCHEMA public TO \"$NEW_USER\";'; \
psql -U \$POSTGRES_USER -d \"$NEW_USER\" -c 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"$NEW_USER\";'"
Conf and env with production database
Make a production conf file based on your app.conf.
cp conf/app.conf conf/prod.conf
echo "conf/prod.conf" >> .gitignore
Modify conf/prod.conf to have your production database creds. Ensure that you replace USERNAME and PASSWORD with your generated credentials.
sessionproviderconfig = postgres://USERNAME:PASSWORD@postgres:5432/USERNAME?sslmode=disable
Also make a .env.prod
cp .env .env.prod
echo ".env.prod" >> .gitignore
Modify it to have your variables.
DATABASE_DRIVER=postgres # sqlite3
DATABASE_URL=postgres://USERNAME:PASSWORD@postgres:5432/USERNAME?sslmode=disable
POSTGRES_USER=USERNAME
POSTGRES_PASSWORD=PASSWORD
Modify Dockerfile to use the prod.conf.
# Copy the rest of the application code from host to image
COPY . .
# NEW: Overwrite the app.conf with prod version
COPY ./conf/prod.conf ./conf/app.conf
#### .........
# Ensure you have all of these COPY statements.
COPY --from=builder /app/server .
COPY --from=builder /app/static ./static/
COPY --from=builder /app/models ./models/
COPY --from=builder /app/utils ./utils/
COPY --from=builder /app/views ./views/
COPY --from=builder /app/conf/app.conf ./conf/
COPY --from=builder /app/go.* .
COPY --from=builder /app/scripts ./scripts/
COPY .env.prod .env
Deploy and see if it works!
You can create a user with:
CONTAINER_NAME=myapp
docker exec -it $CONTAINER_NAME go run scripts/create_user.go